@@ -345,6 +345,48 @@ def func():
345345 handle = thread .start_joinable_thread (func , handle = None )
346346 handle .join ()
347347
348+ class StartNewThreadKwargsRace (unittest .TestCase ):
349+
350+ @unittest .skipUnless (support .Py_GIL_DISABLED , "GIL must be disabled" )
351+ def test_dict_growsup_when_thread_start (self ):
352+ # See gh-149816 - (62) Concurrent kwargs growth causes heap overwrite
353+ # This test is meant to be run under a free-threaded build, where the GIL is
354+ # disabled and concurrent mutations of the same dict can cause heap
355+ # corruption.
356+ results = []
357+ def mutator (shared , stop , prefix , burst ):
358+ i = 0
359+ while not stop .locked ():
360+ for _ in range (burst ):
361+ shared [f"{ prefix } _{ i } " ] = i
362+ i += 1
363+ time .sleep (0 )
364+ results .append (prefix )
365+
366+ def nop (i , ** kwargs ):
367+ pass
368+
369+ DELAY = 1.0
370+ stop = thread .lock ()
371+ shared = {f"base_{ i } " : i for i in range (20000 )}
372+ n = 4
373+ for i in range (n ):
374+ args = (shared , stop , f"dynamic_{ i } " , 1000 )
375+ thread .start_new_thread (mutator , args )
376+
377+ snt = 32
378+ for i in range (snt ):
379+ try :
380+ thread .start_new_thread (nop , (i ,), shared )
381+ except RuntimeError :
382+ break
383+
384+ stop .acquire ()
385+ # wait for all mutator threads stop.
386+ wait_t = time .monotonic ()
387+ while len (results ) < n and time .monotonic () - wait_t < DELAY :
388+ time .sleep (0.01 )
389+
348390
349391class Barrier :
350392 def __init__ (self , num_threads ):
0 commit comments