@@ -2088,6 +2088,14 @@ class _TestFinalize(BaseTestCase):
20882088
20892089 ALLOWED_TYPES = ('processes' ,)
20902090
2091+ def setUp (self ):
2092+ self .registry_backup = util ._finalizer_registry .copy ()
2093+ util ._finalizer_registry .clear ()
2094+
2095+ def tearDown (self ):
2096+ self .assertFalse (util ._finalizer_registry )
2097+ util ._finalizer_registry .update (self .registry_backup )
2098+
20912099 @classmethod
20922100 def _test_finalize (cls , conn ):
20932101 class Foo (object ):
@@ -2137,6 +2145,59 @@ def test_finalize(self):
21372145 result = [obj for obj in iter (conn .recv , 'STOP' )]
21382146 self .assertEqual (result , ['a' , 'b' , 'd10' , 'd03' , 'd02' , 'd01' , 'e' ])
21392147
2148+ def test_thread_safety (self ):
2149+ # bpo-24484: _run_finalizers() should be thread-safe
2150+ def cb ():
2151+ pass
2152+
2153+ class Foo (object ):
2154+ def __init__ (self ):
2155+ self .ref = self # create reference cycle
2156+ # insert finalizer at random key
2157+ util .Finalize (self , cb , exitpriority = random .randint (1 , 100 ))
2158+
2159+ finish = False
2160+ exc = []
2161+
2162+ def run_finalizers ():
2163+ while not finish :
2164+ time .sleep (random .random () * 1e-1 )
2165+ try :
2166+ # A GC run will eventually happen during this,
2167+ # collecting stale Foo's and mutating the registry
2168+ util ._run_finalizers ()
2169+ except Exception as e :
2170+ exc .append (e )
2171+
2172+ def make_finalizers ():
2173+ d = {}
2174+ while not finish :
2175+ try :
2176+ # Old Foo's get gradually replaced and later
2177+ # collected by the GC (because of the cyclic ref)
2178+ d [random .getrandbits (5 )] = {Foo () for i in range (10 )}
2179+ except Exception as e :
2180+ exc .append (e )
2181+ d .clear ()
2182+
2183+ old_interval = sys .getcheckinterval ()
2184+ old_threshold = gc .get_threshold ()
2185+ try :
2186+ sys .setcheckinterval (10 )
2187+ gc .set_threshold (5 , 5 , 5 )
2188+ threads = [threading .Thread (target = run_finalizers ),
2189+ threading .Thread (target = make_finalizers )]
2190+ with test_support .start_threads (threads ):
2191+ time .sleep (4.0 ) # Wait a bit to trigger race condition
2192+ finish = True
2193+ if exc :
2194+ raise exc [0 ]
2195+ finally :
2196+ sys .setcheckinterval (old_interval )
2197+ gc .set_threshold (* old_threshold )
2198+ gc .collect () # Collect remaining Foo's
2199+
2200+
21402201#
21412202# Test that from ... import * works for each module
21422203#
0 commit comments