@@ -437,5 +437,61 @@ def writer():
437437
438438 threading_helper .run_concurrently ([reader , writer ])
439439
440+ def test_racing_dict_update_and_method_lookup_with_inline_values (self ):
441+ # gh-149816: sub-case 108
442+ # The race below checks that a detached dict is still valid
443+ # when racing setattr with __dict__ replacement
444+ class Target :
445+ pass
446+
447+ def appender (obj : Target , start : Barrier , iter_times : int ) -> None :
448+ start .wait ()
449+ index = 0
450+ for _ in range (iter_times ):
451+ setattr (obj , f"probe_{ index } " , index )
452+ index += 1
453+ time .sleep (0 )
454+
455+ def replacer (obj : Target , start : Barrier , iter_times : int , churn_size : int ) -> None :
456+ start .wait ()
457+ for _ in range (iter_times ):
458+ old_dict = obj .__dict__
459+ obj .__dict__ = {}
460+ del old_dict
461+ time .sleep (0 )
462+ # create a list of dicts to trigger a realloc of the dict's table
463+ # and ensure that the old dict is not used after it is deleted
464+ realloc_trigger_list = [{"k" : j } for j in range (churn_size )]
465+ del realloc_trigger_list
466+
467+ def race (iter_times : int ,
468+ churn_size : int ,
469+ appender_threads : int ,
470+ replacer_threads : int ) -> None :
471+ obj = Target ()
472+ setattr (obj , "origin" , 0 )
473+ _ = obj .__dict__ # Access __dict__ to ensure it's initialized
474+
475+ start = Barrier (appender_threads + replacer_threads )
476+ threads = []
477+ for _ in range (appender_threads ):
478+ threads .append (Thread (target = appender , args = (obj , start , iter_times ),
479+ name = "appender" ))
480+ for _ in range (replacer_threads ):
481+ threads .append (Thread (target = replacer , args = (obj , start , iter_times , churn_size ),
482+ name = "replacer" ))
483+
484+ with threading_helper .catch_threading_exception () as cm :
485+ with threading_helper .start_threads (threads ):
486+ pass
487+ if cm .exc_type is not None :
488+ raise cm .exc_value
489+
490+ ITER_TIMES = 2_000
491+ APPENDER_THREADS = 8
492+ REPLACER_THREADS = 8
493+ CHURN_SIZE = 512
494+ race (ITER_TIMES , CHURN_SIZE , APPENDER_THREADS , REPLACER_THREADS )
495+
440496if __name__ == "__main__" :
441497 unittest .main ()
0 commit comments