[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

`#

`