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

`#

`