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 1+ import gc
2+ import unittest
3+ from threading import Event , Thread
4+
5+ from test .support import threading_helper
6+
7+
8+ @threading_helper .requires_working_threading ()
9+ class TestFrozenDict (unittest .TestCase ):
10+ def test_racing_reads_during_construction (self ):
11+ # gh-151722: a frozendict is GC-tracked before it is fully
12+ # populated, so a half-built instance is observable from other
13+ # threads. Reading it with len()/repr()/hash() must not race
14+ # with the construction-time table and ma_used writes.
15+ NUM_KEYS = 8192
16+ NUM_ROUNDS = 40
17+
18+ latest = [frozendict ()] # main -> reader handoff, never empty
19+ done = Event ()
20+
21+ class Evil :
22+ def keys (self ):
23+ return [f"k{ i } " for i in range (NUM_KEYS )]
24+
25+ def __getitem__ (self , key ):
26+ if latest [0 ] is empty :
27+ for obj in gc .get_objects ():
28+ if (isinstance (obj , frozendict )
29+ and 0 < len (obj ) < NUM_KEYS ):
30+ latest [0 ] = obj # leak the half-built object
31+ break
32+ return 1
33+
34+ empty = latest [0 ]
35+
36+ def reader ():
37+ while not done .is_set ():
38+ fd = latest [0 ]
39+ len (fd )
40+ repr (fd )
41+ hash (fd )
42+
43+ readers = [Thread (target = reader ) for _ in range (3 )]
44+ for t in readers :
45+ t .start ()
46+ try :
47+ for _ in range (NUM_ROUNDS ):
48+ latest [0 ] = empty
49+ frozendict (Evil ())
50+ finally :
51+ done .set ()
52+ for t in readers :
53+ t .join ()
54+
55+
56+ if __name__ == "__main__" :
57+ unittest .main ()
You can’t perform that action at this time.
0 commit comments