From bbc03598f7f43e1cca8b4c68e7e2f861e93bbeb8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 15:28:07 +0200 Subject: [PATCH 1/2] gh-151593: Fix dead lock in PyDict insert_split_key() Do not hold LOCK_KEYS() lock when calling PyType_Modified() to avoid a deadlock. Co-authored-by: Neil Schemenauer --- Objects/dictobject.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 9210398ee551de1..e6ee291ad668779 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1923,12 +1923,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 +1939,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; } From d1a244142a10eb6233d93b07c6e1c9cc6ac4278b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 16:30:21 +0200 Subject: [PATCH 2/2] Add a comment on LOCK_KEYS() explaining _Py_LOCK_DONT_DETACH --- Objects/dictobject.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e6ee291ad668779..7c13247d98e3786 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)