diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 9210398ee551de..7c13247d98e378 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -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; }