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]

Reply via email to