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
This commit is contained in:
Victor Stinner 2017-08-10 16:01:47 +02:00 committed by GitHub
parent aa8ec34ad5
commit b501147980
2 changed files with 73 additions and 17 deletions

View file

@ -2073,26 +2073,35 @@ def decorator(*args):
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):

View file

@ -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 @@ def test_check__all__(self):
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 @@ def test_check__all__(self):
# run_doctest
# threading_cleanup
# reap_threads
# reap_children
# strip_python_stderr
# args_from_interpreter_flags
# can_symlink