Skip to content

Commit b2450ab

Browse files
committed
pythongh-104090: Fix unittest collectedDurations resources leak
1 parent be1b968 commit b2450ab

File tree

3 files changed

+37
-2
lines changed

3 files changed

+37
-2
lines changed

Lib/multiprocessing/resource_tracker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __init__(self):
5757
self._lock = threading.Lock()
5858
self._fd = None
5959
self._pid = None
60+
self._exitcode = None
6061

6162
def _stop(self):
6263
with self._lock:
@@ -68,7 +69,7 @@ def _stop(self):
6869
os.close(self._fd)
6970
self._fd = None
7071

71-
os.waitpid(self._pid, 0)
72+
_, self._exitcode = os.waitpid(self._pid, 0)
7273
self._pid = None
7374

7475
def getfd(self):
@@ -191,6 +192,8 @@ def main(fd):
191192
pass
192193

193194
cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()}
195+
exit_code = 0
196+
194197
try:
195198
# keep track of registered/unregistered resources
196199
with open(fd, 'rb') as f:
@@ -221,6 +224,7 @@ def main(fd):
221224
for rtype, rtype_cache in cache.items():
222225
if rtype_cache:
223226
try:
227+
exit_code = 1
224228
warnings.warn('resource_tracker: There appear to be %d '
225229
'leaked %s objects to clean up at shutdown' %
226230
(len(rtype_cache), rtype))
@@ -237,3 +241,5 @@ def main(fd):
237241
warnings.warn('resource_tracker: %r: %s' % (name, e))
238242
finally:
239243
pass
244+
245+
sys.exit(exit_code)

Lib/test/test_concurrent_futures.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,34 @@ def _assert_logged(self, msg):
290290
create_executor_tests(FailingInitializerMixin)
291291

292292

293+
class FailingInitializerResourcesTest(unittest.TestCase):
294+
"""
295+
Source: https://github.com/python/cpython/issues/104090
296+
"""
297+
298+
def _test(self, test_class):
299+
runner = unittest.TextTestRunner()
300+
result = runner.run(test_class('test_initializer'))
301+
302+
self.assertEqual(result.testsRun, 1)
303+
self.assertEqual(result.failures, [])
304+
self.assertEqual(result.errors, [])
305+
306+
# GH-104090:
307+
# Stop resource tracker manually now, so we can verify there are not leaked resources by checking
308+
# the process exit code
309+
from multiprocessing.resource_tracker import _resource_tracker
310+
_resource_tracker._stop()
311+
312+
self.assertEqual(_resource_tracker._exitcode, 0)
313+
314+
def test_spawn(self):
315+
self._test(ProcessPoolSpawnFailingInitializerTest)
316+
317+
def test_forkserver(self):
318+
self._test(ProcessPoolForkserverFailingInitializerTest)
319+
320+
293321
class ExecutorShutdownTest:
294322
def test_run_after_shutdown(self):
295323
self.executor.shutdown()

Lib/unittest/result.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ def addDuration(self, test, elapsed):
166166
"""
167167
# support for a TextTestRunner using an old TestResult class
168168
if hasattr(self, "collectedDurations"):
169-
self.collectedDurations.append((test, elapsed))
169+
# Pass test repr and not the test object itself to avoid resources leak
170+
self.collectedDurations.append((str(test), elapsed))
170171

171172
def wasSuccessful(self):
172173
"""Tells whether or not this result was a success."""

0 commit comments

Comments
 (0)