https://github.com/python/cpython/commit/6841d98c1571bb48c05101059b25e28ba37f68f7 commit: 6841d98c1571bb48c05101059b25e28ba37f68f7 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 <[email protected]> date: 2026-10-26T14:46:27Z summary:
[3.14] gh-160496: Clear inline values with atomic release stores (GH-160510) (#160709) gh-160496: Clear inline values with atomic release stores (GH-160510) (cherry picked from commit a9049ee4fb62138e4aa03c06f2e705a03677f92e) Co-authored-by: mpage <[email protected]> files: M Lib/test/test_free_threading/test_dict.py M Objects/dictobject.c diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py index b600f56687c9158..a3680615376dd5f 100644 --- a/Lib/test/test_free_threading/test_dict.py +++ b/Lib/test/test_free_threading/test_dict.py @@ -228,6 +228,39 @@ def reader_func(): self.assertEqual(count, 0) + def test_racing_replace_inline_values_and_lookup(self): + # gh-160496: replacing an unmaterialized __dict__ clears inline + # values while attribute lookup can still read them without a lock. + class C: + pass + + objects = [C() for _ in range(1000)] + for obj in objects: + obj.x = [0] + + # Inline values are invalidated only once per object, so synchronize + # the threads for each fresh instance without accessing its __dict__. + barrier = Barrier(2) + observed = [] + + def writer(): + for obj in objects: + barrier.wait() + obj.__dict__ = {"x": [1]} + barrier.wait() + + def reader(): + for obj in objects: + barrier.wait() + observed.append(getattr(obj, "x", None)) + barrier.wait() + + threading_helper.run_concurrently([writer, reader]) + for value in observed: + self.assertIn(value, ([0], [1])) + for obj in objects: + self.assertEqual(obj.x, [1]) + def test_racing_object_get_set_dict(self): e = Exception() diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c579ff47692f7b0..e48585a558f5d09 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -7285,7 +7285,11 @@ clear_inline_values(PyDictValues *values) if (values->valid) { FT_ATOMIC_STORE_UINT8(values->valid, 0); for (Py_ssize_t i = 0; i < values->capacity; i++) { - Py_CLEAR(values->values[i]); + PyObject *value = values->values[i]; + // The attribute read fast-path may read these without holding the + // per-object lock. + FT_ATOMIC_STORE_PTR_RELEASE(values->values[i], NULL); + Py_XDECREF(value); } } } _______________________________________________ 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]
