test_multiprocessing detects dangling per test case (#2841) · python/cpython@ffb4940
@@ -4303,7 +4303,32 @@ def test_empty(self):
43034303# Mixins
43044304#
430543054306-class ProcessesMixin(object):
4306+class BaseMixin(object):
4307+@classmethod
4308+def setUpClass(cls):
4309+cls.dangling = (multiprocessing.process._dangling.copy(),
4310+threading._dangling.copy())
4311+4312+@classmethod
4313+def tearDownClass(cls):
4314+# bpo-26762: Some multiprocessing objects like Pool create reference
4315+# cycles. Trigger a garbage collection to break these cycles.
4316+test.support.gc_collect()
4317+4318+processes = set(multiprocessing.process._dangling) - set(cls.dangling[0])
4319+if processes:
4320+print('Warning -- Dangling processes: %s' % processes,
4321+file=sys.stderr)
4322+processes = None
4323+4324+threads = set(threading._dangling) - set(cls.dangling[1])
4325+if threads:
4326+print('Warning -- Dangling threads: %s' % threads,
4327+file=sys.stderr)
4328+threads = None
4329+4330+4331+class ProcessesMixin(BaseMixin):
43074332TYPE = 'processes'
43084333Process = multiprocessing.Process
43094334connection = multiprocessing.connection
@@ -4326,7 +4351,7 @@ class ProcessesMixin(object):
43264351RawArray = staticmethod(multiprocessing.RawArray)
43274352432843534329-class ManagerMixin(object):
4354+class ManagerMixin(BaseMixin):
43304355TYPE = 'manager'
43314356Process = multiprocessing.Process
43324357Queue = property(operator.attrgetter('manager.Queue'))
@@ -4350,30 +4375,43 @@ def Pool(cls, *args, **kwds):
4350437543514376@classmethod
43524377def setUpClass(cls):
4378+super().setUpClass()
43534379cls.manager = multiprocessing.Manager()
4354438043554381@classmethod
43564382def tearDownClass(cls):
43574383# only the manager process should be returned by active_children()
43584384# but this can take a bit on slow machines, so wait a few seconds
43594385# if there are other children too (see #17395)
4386+start_time = time.monotonic()
43604387t = 0.01
4361-while len(multiprocessing.active_children()) > 1 and t < 5:
4388+while len(multiprocessing.active_children()) > 1:
43624389time.sleep(t)
43634390t *= 2
4391+dt = time.monotonic() - start_time
4392+if dt >= 5.0:
4393+print("Warning -- multiprocessing.Manager still has %s active "
4394+"children after %s seconds"
4395+% (multiprocessing.active_children(), dt),
4396+file=sys.stderr)
4397+break
4398+43644399gc.collect() # do garbage collection
43654400if cls.manager._number_of_objects() != 0:
43664401# This is not really an error since some tests do not
43674402# ensure that all processes which hold a reference to a
43684403# managed object have been joined.
4369-print('Shared objects which still exist at manager shutdown:')
4404+print('Warning -- Shared objects which still exist at manager '
4405+'shutdown:')
43704406print(cls.manager._debug_info())
43714407cls.manager.shutdown()
43724408cls.manager.join()
43734409cls.manager = None
437444104411+super().tearDownClass()
4412+437544134376-class ThreadsMixin(object):
4414+class ThreadsMixin(BaseMixin):
43774415TYPE = 'threads'
43784416Process = multiprocessing.dummy.Process
43794417connection = multiprocessing.dummy.connection
@@ -4450,18 +4488,33 @@ def setUpModule():
44504488multiprocessing.get_logger().setLevel(LOG_LEVEL)
4451448944524490def tearDownModule():
4491+need_sleep = False
4492+4493+# bpo-26762: Some multiprocessing objects like Pool create reference
4494+# cycles. Trigger a garbage collection to break these cycles.
4495+test.support.gc_collect()
4496+44534497multiprocessing.set_start_method(old_start_method[0], force=True)
44544498# pause a bit so we don't get warning about dangling threads/processes
4455-time.sleep(0.5)
4499+processes = set(multiprocessing.process._dangling) - set(dangling[0])
4500+if processes:
4501+need_sleep = True
4502+print('Warning -- Dangling processes: %s' % processes,
4503+file=sys.stderr)
4504+processes = None
4505+4506+threads = set(threading._dangling) - set(dangling[1])
4507+if threads:
4508+need_sleep = True
4509+print('Warning -- Dangling threads: %s' % threads,
4510+file=sys.stderr)
4511+threads = None
4512+4513+# Sleep 500 ms to give time to child processes to complete.
4514+if need_sleep:
4515+time.sleep(0.5)
44564516multiprocessing.process._cleanup()
4457-gc.collect()
4458-tmp = set(multiprocessing.process._dangling) - set(dangling[0])
4459-if tmp:
4460-print('Dangling processes:', tmp, file=sys.stderr)
4461-del tmp
4462-tmp = set(threading._dangling) - set(dangling[1])
4463-if tmp:
4464-print('Dangling threads:', tmp, file=sys.stderr)
4517+test.support.gc_collect()
4465451844664519remote_globs['setUpModule'] = setUpModule
44674520remote_globs['tearDownModule'] = tearDownModule