https://github.com/python/cpython/commit/bef570622263ecd58563f0474693c19c8545de73
commit: bef570622263ecd58563f0474693c19c8545de73
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-06-25T19:58:27+02:00
summary:

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 <[email protected]>

files:
M Objects/dictobject.c

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;
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to