From b5011479808b80545bdd1246725fc7940691b084 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Aug 2017 16:01:47 +0200 Subject: [PATCH] Enhance support.reap_children() (#3036) * reap_children() now sets environment_altered to True to detect bugs using python3 -m test --fail-env-changed * Replace bare "except:" with "except OSError:" in reap_children() * Write an unit test for reap_children() using a timeout of 60 seconds --- Lib/test/support/__init__.py | 33 +++++++++++++-------- Lib/test/test_support.py | 57 ++++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 7a4671c1b2b..0235498e2a9 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2073,26 +2073,35 @@ def reap_threads(func): threading_cleanup(*key) return decorator + def reap_children(): """Use this function at the end of test_main() whenever sub-processes are started. This will help ensure that no extra children (zombies) stick around to hog resources and create problems when looking for refleaks. """ + global environment_altered + + # Need os.waitpid(-1, os.WNOHANG): Windows is not supported + if not (hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG')): + return + # Reap all our dead child processes so we don't leave zombies around. # These hog resources and might be causing some of the buildbots to die. - if hasattr(os, 'waitpid'): - any_process = -1 - while True: - try: - # This will raise an exception on Windows. That's ok. - pid, status = os.waitpid(any_process, os.WNOHANG) - if pid == 0: - break - print("Warning -- reap_children() reaped child process %s" - % pid, file=sys.stderr) - except: - break + while True: + try: + # Read the exit status of any child process which already completed + pid, status = os.waitpid(-1, os.WNOHANG) + except OSError: + break + + if pid == 0: + break + + print("Warning -- reap_children() reaped child process %s" + % pid, file=sys.stderr) + environment_altered = True + @contextlib.contextmanager def start_threads(threads, unlock=None): diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 1e6b2b54e23..8632837780c 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -1,12 +1,15 @@ +import contextlib +import errno import importlib +import io +import os import shutil +import socket import stat import sys -import os -import unittest -import socket import tempfile -import errno +import time +import unittest from test import support TESTFN = support.TESTFN @@ -378,6 +381,51 @@ class TestSupport(unittest.TestCase): self.assertRaises(AssertionError, support.check__all__, self, unittest) + @unittest.skipUnless(hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG'), + 'need os.waitpid() and os.WNOHANG') + def test_reap_children(self): + # Make sure that there is no other pending child process + support.reap_children() + + # Create a child process + pid = os.fork() + if pid == 0: + # child process: do nothing, just exit + os._exit(0) + + t0 = time.monotonic() + deadline = time.monotonic() + 60.0 + + was_altered = support.environment_altered + try: + support.environment_altered = False + stderr = io.StringIO() + + while True: + if time.monotonic() > deadline: + self.fail("timeout") + + with contextlib.redirect_stderr(stderr): + support.reap_children() + + # Use environment_altered to check if reap_children() found + # the child process + if support.environment_altered: + break + + # loop until the child process completed + time.sleep(0.100) + + msg = "Warning -- reap_children() reaped child process %s" % pid + self.assertIn(msg, stderr.getvalue()) + self.assertTrue(support.environment_altered) + finally: + support.environment_altered = was_altered + + # Just in case, check again that there is no other + # pending child process + support.reap_children() + # XXX -follows a list of untested API # make_legacy_pyc # is_resource_enabled @@ -398,7 +446,6 @@ class TestSupport(unittest.TestCase): # run_doctest # threading_cleanup # reap_threads - # reap_children # strip_python_stderr # args_from_interpreter_flags # can_symlink