From 06f9c8ca1cb9abe92507108a299a65ee868d07e3 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 30 Jan 2026 12:15:47 -0500 Subject: [PATCH] [3.14] gh-144295: Fix data race in dict method lookup and global load (gh-144312) (#144346) In `_Py_dict_lookup_threadsafe_stackref`, call `ensure_shared_on_read()` to prevent a race between the lookup and concurrent dict resizes, which may free the PyDictKeysObject (i.e., it ensures that the resize uses QSBR). (cherry picked from commit e666a01ef42939f77f4c22ca47a610df5ef8b7ab) --- Lib/test/test_free_threading/test_dict.py | 22 ++++++++++++++++++++++ Objects/dictobject.c | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py index 5d5d4e226caa..1ffd924e9f47 100644 --- a/Lib/test/test_free_threading/test_dict.py +++ b/Lib/test/test_free_threading/test_dict.py @@ -245,5 +245,27 @@ class TestDict(TestCase): with threading_helper.start_threads([t1, t2]): pass + def test_racing_dict_update_and_method_lookup(self): + # gh-144295: test race between dict modifications and method lookups. + # Uses BytesIO because the race requires a type without Py_TPFLAGS_INLINE_VALUES + # for the _PyDict_GetMethodStackRef code path. + import io + obj = io.BytesIO() + + def writer(): + for _ in range(10000): + obj.x = 1 + del obj.x + + def reader(): + for _ in range(10000): + obj.getvalue() + + t1 = Thread(target=writer) + t2 = Thread(target=reader) + + with threading_helper.start_threads([t1, t2]): + pass + if __name__ == "__main__": unittest.main() diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e7d5088ded7b..c2223af95006 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1587,7 +1587,9 @@ read_failed: Py_ssize_t _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) { - PyDictKeysObject *dk = _Py_atomic_load_ptr(&mp->ma_keys); + ensure_shared_on_read(mp); + + PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys); if (dk->dk_kind == DICT_KEYS_UNICODE && PyUnicode_CheckExact(key)) { Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); if (ix == DKIX_EMPTY) { -- 2.47.3