From 8b7ee11a5bcdd5e56e82fbb887b3be0396fc30ff Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Mon, 18 May 2026 21:39:45 +0530 Subject: [PATCH] gh-149816: fix `dict.clear()` race on split-table dict with non-embedded values (GH-149914) (cherry picked from commit 169285470630b697c5e6e0e4c8091c31f25ffb04) Co-authored-by: Kumar Aditya --- Lib/test/test_free_threading/test_dict.py | 28 +++++++++++++++++++++++ Objects/dictobject.c | 6 +++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py index 55272a00c3ad50..dfe0634211d4b0 100644 --- a/Lib/test/test_free_threading/test_dict.py +++ b/Lib/test/test_free_threading/test_dict.py @@ -268,6 +268,34 @@ def watcher(): finally: _testcapi.clear_dict_watcher(wid) + def test_racing_split_dict_clear_and_lookup(self): + class C: + pass + + keys = [f"a{i}" for i in range(16)] + + def make_split_nonembedded(): + inst = C() + for key in keys: + setattr(inst, key, keys.index(key)) + # dict.copy() of a split instance dict yields a split table + # with non-embedded values + return inst.__dict__.copy() + + d = make_split_nonembedded() + + def clearer(): + for _ in range(1000): + d.clear() + d.update(make_split_nonembedded()) + + def reader(): + for _ in range(1000): + for k in keys: + d.get(k) + + threading_helper.run_concurrently([clearer, reader, reader]) + 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 diff --git a/Objects/dictobject.c b/Objects/dictobject.c index b33a273dac3b95..a7d67812bec925 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3083,10 +3083,12 @@ clear_lock_held(PyObject *op) set_keys(mp, Py_EMPTY_KEYS); n = oldkeys->dk_nentries; for (i = 0; i < n; i++) { - Py_CLEAR(oldvalues->values[i]); + PyObject *tmp = oldvalues->values[i]; + FT_ATOMIC_STORE_PTR_RELEASE(oldvalues->values[i], NULL); + Py_XDECREF(tmp); } free_values(oldvalues, IS_DICT_SHARED(mp)); - dictkeys_decref(oldkeys, false); + dictkeys_decref(oldkeys, IS_DICT_SHARED(mp)); } ASSERT_CONSISTENT(mp); }