gh-151593: Fix dead lock in PyDict insert_split_key() (#152200)

Do not hold LOCK_KEYS() lock when calling PyType_Modified() to avoid
a deadlock.

Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
This commit is contained in:
Victor Stinner 2026-06-25 19:58:27 +02:00 committed by GitHub
parent 7c8163719c
commit bef5706222
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -214,6 +214,15 @@ set_values(PyDictObject *mp, PyDictValues *values)
_Py_atomic_store_ptr_release(&mp->ma_values, values);
}
// gh-151593: The _Py_LOCK_DONT_DETACH flag ensures that the outer critical
// section is not dropped if there is some contention on the keys lock.
// It also means that it will be important that LOCK_KEYS() is essentially the
// "inner-most" code and that we don't call Py_DECREF() or similar while
// holding the keys lock.
//
// We are not allowed to acquire other locks within LOCK_KEYS(). For example,
// PyType_Modified() must not be called within LOCK_KEYS() since it acquires
// the type lock.
#define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH)
#define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex)
@ -1923,12 +1932,13 @@ insert_split_key(PyDictKeysObject *keys, PyObject *key, Py_hash_t hash)
}
#endif
bool inserted = false;
LOCK_KEYS(keys);
ix = unicodekeys_lookup_unicode(keys, key, hash);
if (ix == DKIX_EMPTY && keys->dk_usable > 0) {
// Insert into new slot
inserted = true;
FT_ATOMIC_STORE_UINT32_RELAXED(keys->dk_version, 0);
_PyDict_SplitKeysInvalidated(keys);
Py_ssize_t hashpos = find_empty_slot(keys, hash);
ix = keys->dk_nentries;
dictkeys_set_index(keys, hashpos, ix);
@ -1938,6 +1948,13 @@ insert_split_key(PyDictKeysObject *keys, PyObject *key, Py_hash_t hash)
}
assert (ix < SHARED_KEYS_MAX_SIZE);
UNLOCK_KEYS(keys);
if (inserted) {
// gh-151593: Calling PyType_Modified() with LOCK_KEYS() creates a
// deadlock. So only call the function after UNLOCK_KEYS().
_PyDict_SplitKeysInvalidated(keys);
}
return ix;
}