File tree Expand file tree Collapse file tree
Lib/test/test_free_threading Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -228,6 +228,39 @@ def reader_func():
228228
229229 self .assertEqual (count , 0 )
230230
231+ def test_racing_replace_inline_values_and_lookup (self ):
232+ # gh-160496: replacing an unmaterialized __dict__ clears inline
233+ # values while attribute lookup can still read them without a lock.
234+ class C :
235+ pass
236+
237+ objects = [C () for _ in range (1000 )]
238+ for obj in objects :
239+ obj .x = [0 ]
240+
241+ # Inline values are invalidated only once per object, so synchronize
242+ # the threads for each fresh instance without accessing its __dict__.
243+ barrier = Barrier (2 )
244+ observed = []
245+
246+ def writer ():
247+ for obj in objects :
248+ barrier .wait ()
249+ obj .__dict__ = {"x" : [1 ]}
250+ barrier .wait ()
251+
252+ def reader ():
253+ for obj in objects :
254+ barrier .wait ()
255+ observed .append (getattr (obj , "x" , None ))
256+ barrier .wait ()
257+
258+ threading_helper .run_concurrently ([writer , reader ])
259+ for value in observed :
260+ self .assertIn (value , ([0 ], [1 ]))
261+ for obj in objects :
262+ self .assertEqual (obj .x , [1 ])
263+
231264 def test_racing_object_get_set_dict (self ):
232265 e = Exception ()
233266
Original file line number Diff line number Diff line change @@ -7731,7 +7731,11 @@ clear_inline_values(PyDictValues *values)
77317731 if (values -> valid ) {
77327732 FT_ATOMIC_STORE_UINT8 (values -> valid , 0 );
77337733 for (Py_ssize_t i = 0 ; i < values -> capacity ; i ++ ) {
7734- Py_CLEAR (values -> values [i ]);
7734+ PyObject * value = values -> values [i ];
7735+ // The attribute read fast-path may read these without holding the
7736+ // per-object lock.
7737+ FT_ATOMIC_STORE_PTR_RELEASE (values -> values [i ], NULL );
7738+ Py_XDECREF (value );
77357739 }
77367740 }
77377741}
You can’t perform that action at this time.
0 commit comments