@@ -358,8 +358,8 @@ def writer():
358358
359359 def test_racing_dict_update_and_method_lookup_with_inline_values (self ):
360360 # gh-149816: sub-case 108
361- # The race below checks that setattr() does not write into a detached
362- # dict after __dict__ is replaced by another thread.
361+ # The race below checks that a detached dict is still valid
362+ # when racing setattr with __dict__ replacement
363363 class Target :
364364 pass
365365
@@ -371,44 +371,46 @@ def appender(obj: Target, start: Barrier, iter_times: int) -> None:
371371 index += 1
372372 time .sleep (0 )
373373
374- def replacer (obj : Target , start : Barrier , iter_times : int ,
375- detached_dict_queue : list [tuple [dict , frozenset ]]) -> None :
374+ def replacer (obj : Target , start : Barrier , iter_times : int , churn_size : int ) -> None :
376375 start .wait ()
377376 for _ in range (iter_times ):
378- dict_snapshot = obj .__dict__
377+ old_dict = obj .__dict__
379378 obj .__dict__ = {}
380- current_keys = frozenset (dict_snapshot )
381- detached_dict_queue .append ((dict_snapshot , current_keys ))
379+ del old_dict
382380 time .sleep (0 )
383-
384- def race (iter_times : int , appender_threads : int ,
381+ # create a list of dicts to trigger a realloc of the dict's table
382+ # and ensure that the old dict is not used after it is deleted
383+ realloc_trigger_list = [{"k" : j } for j in range (churn_size )]
384+ del realloc_trigger_list
385+
386+ def race (iter_times : int ,
387+ churn_size : int ,
388+ appender_threads : int ,
385389 replacer_threads : int ) -> None :
386390 obj = Target ()
387391 setattr (obj , "origin" , 0 )
388392 _ = obj .__dict__ # Access __dict__ to ensure it's initialized
389393
390394 start = Barrier (appender_threads + replacer_threads )
391- detached_dict_queue : list [tuple [dict , frozenset ]] = []
392395 threads = []
393396 for _ in range (appender_threads ):
394397 threads .append (Thread (target = appender , args = (obj , start , iter_times ),
395398 name = "appender" ))
396399 for _ in range (replacer_threads ):
397- threads .append (Thread (target = replacer , args = (obj , start , iter_times , detached_dict_queue ),
400+ threads .append (Thread (target = replacer , args = (obj , start , iter_times , churn_size ),
398401 name = "replacer" ))
399402
400- with threading_helper .start_threads (threads ):
401- pass
402-
403- for dict_snapshot , current_keys in detached_dict_queue :
404- self .assertEqual (set (dict_snapshot ), current_keys ,
405- f"Detached dict keys { set (dict_snapshot )} " +
406- f"do not match current keys { current_keys } " )
403+ with threading_helper .catch_threading_exception () as cm :
404+ with threading_helper .start_threads (threads ):
405+ pass
406+ if cm .exc_type is not None :
407+ raise cm .exc_value
407408
408- ITER_TIMES = 50_000
409+ ITER_TIMES = 2_000
409410 APPENDER_THREADS = 8
410411 REPLACER_THREADS = 8
411- race (ITER_TIMES , APPENDER_THREADS , REPLACER_THREADS )
412+ CHURN_SIZE = 512
413+ race (ITER_TIMES , CHURN_SIZE , APPENDER_THREADS , REPLACER_THREADS )
412414
413415if __name__ == "__main__" :
414416 unittest .main ()
0 commit comments