bpo-24484: Avoid race condition in multiprocessing cleanup (#2159) · python/cpython@1eb6c00 (original) (raw)
`@@ -3110,6 +3110,14 @@ class _TestFinalize(BaseTestCase):
`
3110
3110
``
3111
3111
`ALLOWED_TYPES = ('processes',)
`
3112
3112
``
``
3113
`+
def setUp(self):
`
``
3114
`+
self.registry_backup = util._finalizer_registry.copy()
`
``
3115
`+
util._finalizer_registry.clear()
`
``
3116
+
``
3117
`+
def tearDown(self):
`
``
3118
`+
self.assertFalse(util._finalizer_registry)
`
``
3119
`+
util._finalizer_registry.update(self.registry_backup)
`
``
3120
+
3113
3121
`@classmethod
`
3114
3122
`def _test_finalize(cls, conn):
`
3115
3123
`class Foo(object):
`
`@@ -3159,6 +3167,61 @@ def test_finalize(self):
`
3159
3167
`result = [obj for obj in iter(conn.recv, 'STOP')]
`
3160
3168
`self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e'])
`
3161
3169
``
``
3170
`+
def test_thread_safety(self):
`
``
3171
`+
bpo-24484: _run_finalizers() should be thread-safe
`
``
3172
`+
def cb():
`
``
3173
`+
pass
`
``
3174
+
``
3175
`+
class Foo(object):
`
``
3176
`+
def init(self):
`
``
3177
`+
self.ref = self # create reference cycle
`
``
3178
`+
insert finalizer at random key
`
``
3179
`+
util.Finalize(self, cb, exitpriority=random.randint(1, 100))
`
``
3180
+
``
3181
`+
finish = False
`
``
3182
`+
exc = None
`
``
3183
+
``
3184
`+
def run_finalizers():
`
``
3185
`+
nonlocal exc
`
``
3186
`+
while not finish:
`
``
3187
`+
time.sleep(random.random() * 1e-1)
`
``
3188
`+
try:
`
``
3189
`+
A GC run will eventually happen during this,
`
``
3190
`+
collecting stale Foo's and mutating the registry
`
``
3191
`+
util._run_finalizers()
`
``
3192
`+
except Exception as e:
`
``
3193
`+
exc = e
`
``
3194
+
``
3195
`+
def make_finalizers():
`
``
3196
`+
nonlocal exc
`
``
3197
`+
d = {}
`
``
3198
`+
while not finish:
`
``
3199
`+
try:
`
``
3200
`+
Old Foo's get gradually replaced and later
`
``
3201
`+
collected by the GC (because of the cyclic ref)
`
``
3202
`+
d[random.getrandbits(5)] = {Foo() for i in range(10)}
`
``
3203
`+
except Exception as e:
`
``
3204
`+
exc = e
`
``
3205
`+
d.clear()
`
``
3206
+
``
3207
`+
old_interval = sys.getswitchinterval()
`
``
3208
`+
old_threshold = gc.get_threshold()
`
``
3209
`+
try:
`
``
3210
`+
sys.setswitchinterval(1e-6)
`
``
3211
`+
gc.set_threshold(5, 5, 5)
`
``
3212
`+
threads = [threading.Thread(target=run_finalizers),
`
``
3213
`+
threading.Thread(target=make_finalizers)]
`
``
3214
`+
with test.support.start_threads(threads):
`
``
3215
`+
time.sleep(4.0) # Wait a bit to trigger race condition
`
``
3216
`+
finish = True
`
``
3217
`+
if exc is not None:
`
``
3218
`+
raise exc
`
``
3219
`+
finally:
`
``
3220
`+
sys.setswitchinterval(old_interval)
`
``
3221
`+
gc.set_threshold(*old_threshold)
`
``
3222
`+
gc.collect() # Collect remaining Foo's
`
``
3223
+
``
3224
+
3162
3225
`#
`
3163
3226
`# Test that from ... import * works for each module
`
3164
3227
`#
`