[3.5] bpo-24484: Avoid race condition in multiprocessing cleanup (GH-… · python/cpython@a0ecaab (original) (raw)
`@@ -2985,6 +2985,14 @@ class _TestFinalize(BaseTestCase):
`
2985
2985
``
2986
2986
`ALLOWED_TYPES = ('processes',)
`
2987
2987
``
``
2988
`+
def setUp(self):
`
``
2989
`+
self.registry_backup = util._finalizer_registry.copy()
`
``
2990
`+
util._finalizer_registry.clear()
`
``
2991
+
``
2992
`+
def tearDown(self):
`
``
2993
`+
self.assertFalse(util._finalizer_registry)
`
``
2994
`+
util._finalizer_registry.update(self.registry_backup)
`
``
2995
+
2988
2996
`@classmethod
`
2989
2997
`def _test_finalize(cls, conn):
`
2990
2998
`class Foo(object):
`
`@@ -3034,6 +3042,61 @@ def test_finalize(self):
`
3034
3042
`result = [obj for obj in iter(conn.recv, 'STOP')]
`
3035
3043
`self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e'])
`
3036
3044
``
``
3045
`+
def test_thread_safety(self):
`
``
3046
`+
bpo-24484: _run_finalizers() should be thread-safe
`
``
3047
`+
def cb():
`
``
3048
`+
pass
`
``
3049
+
``
3050
`+
class Foo(object):
`
``
3051
`+
def init(self):
`
``
3052
`+
self.ref = self # create reference cycle
`
``
3053
`+
insert finalizer at random key
`
``
3054
`+
util.Finalize(self, cb, exitpriority=random.randint(1, 100))
`
``
3055
+
``
3056
`+
finish = False
`
``
3057
`+
exc = None
`
``
3058
+
``
3059
`+
def run_finalizers():
`
``
3060
`+
nonlocal exc
`
``
3061
`+
while not finish:
`
``
3062
`+
time.sleep(random.random() * 1e-1)
`
``
3063
`+
try:
`
``
3064
`+
A GC run will eventually happen during this,
`
``
3065
`+
collecting stale Foo's and mutating the registry
`
``
3066
`+
util._run_finalizers()
`
``
3067
`+
except Exception as e:
`
``
3068
`+
exc = e
`
``
3069
+
``
3070
`+
def make_finalizers():
`
``
3071
`+
nonlocal exc
`
``
3072
`+
d = {}
`
``
3073
`+
while not finish:
`
``
3074
`+
try:
`
``
3075
`+
Old Foo's get gradually replaced and later
`
``
3076
`+
collected by the GC (because of the cyclic ref)
`
``
3077
`+
d[random.getrandbits(5)] = {Foo() for i in range(10)}
`
``
3078
`+
except Exception as e:
`
``
3079
`+
exc = e
`
``
3080
`+
d.clear()
`
``
3081
+
``
3082
`+
old_interval = sys.getswitchinterval()
`
``
3083
`+
old_threshold = gc.get_threshold()
`
``
3084
`+
try:
`
``
3085
`+
sys.setswitchinterval(1e-6)
`
``
3086
`+
gc.set_threshold(5, 5, 5)
`
``
3087
`+
threads = [threading.Thread(target=run_finalizers),
`
``
3088
`+
threading.Thread(target=make_finalizers)]
`
``
3089
`+
with test.support.start_threads(threads):
`
``
3090
`+
time.sleep(4.0) # Wait a bit to trigger race condition
`
``
3091
`+
finish = True
`
``
3092
`+
if exc is not None:
`
``
3093
`+
raise exc
`
``
3094
`+
finally:
`
``
3095
`+
sys.setswitchinterval(old_interval)
`
``
3096
`+
gc.set_threshold(*old_threshold)
`
``
3097
`+
gc.collect() # Collect remaining Foo's
`
``
3098
+
``
3099
+
3037
3100
`#
`
3038
3101
`# Test that from ... import * works for each module
`
3039
3102
`#
`