From: Brij Kapadia <97006829+brijkapadia@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:07:17 +0000 (-0400) Subject: gh-153881: Atomically load `dk_nentries` in `_PyObject_IsInstanceDictEmpty` (#153882) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ebe773160327026ba5393b27e2e2860db4d08b2;p=thirdparty%2FPython%2Fcpython.git gh-153881: Atomically load `dk_nentries` in `_PyObject_IsInstanceDictEmpty` (#153882) --- diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py index ad23290a92ab..4a812275143b 100644 --- a/Lib/test/test_free_threading/test_dict.py +++ b/Lib/test/test_free_threading/test_dict.py @@ -336,5 +336,25 @@ class TestDict(TestCase): with threading_helper.start_threads([t1, t2]): pass + def test_getstate_race_with_shared_keys(self): + box = [None] + enter = Barrier(2) + leave = Barrier(2) + + def reader(): + for _ in range(1000): + enter.wait() + box[0].__getstate__() + leave.wait() + + def writer(): + for i in range(1000): + box[0] = type(f"C{i}", (), {})() + enter.wait() + setattr(box[0], str(i), 1) + leave.wait() + + threading_helper.run_concurrently([reader, writer]) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-17-22-03-39.gh-issue-153881.oDa06s.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-17-22-03-39.gh-issue-153881.oDa06s.rst new file mode 100644 index 000000000000..8facf88367fd --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-17-22-03-39.gh-issue-153881.oDa06s.rst @@ -0,0 +1,2 @@ +Fix potential data race when calling :meth:`~object.__getstate__` +under the :term:`free-threaded build`. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 246ffe6c18e9..c650aa456d2c 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -7722,7 +7722,7 @@ _PyObject_IsInstanceDictEmpty(PyObject *obj) PyDictValues *values = _PyObject_InlineValues(obj); if (FT_ATOMIC_LOAD_UINT8(values->valid)) { PyDictKeysObject *keys = CACHED_KEYS(tp); - for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) { + for (Py_ssize_t i = 0; i < LOAD_KEYS_NENTRIES(keys); i++) { if (FT_ATOMIC_LOAD_PTR_RELAXED(values->values[i]) != NULL) { return 0; }