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:
parent
aa8ec34ad5
commit
b501147980
|
@ -2073,26 +2073,35 @@ def reap_threads(func):
|
||||||
threading_cleanup(*key)
|
threading_cleanup(*key)
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def reap_children():
|
def reap_children():
|
||||||
"""Use this function at the end of test_main() whenever sub-processes
|
"""Use this function at the end of test_main() whenever sub-processes
|
||||||
are started. This will help ensure that no extra children (zombies)
|
are started. This will help ensure that no extra children (zombies)
|
||||||
stick around to hog resources and create problems when looking
|
stick around to hog resources and create problems when looking
|
||||||
for refleaks.
|
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.
|
# 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.
|
# These hog resources and might be causing some of the buildbots to die.
|
||||||
if hasattr(os, 'waitpid'):
|
while True:
|
||||||
any_process = -1
|
try:
|
||||||
while True:
|
# Read the exit status of any child process which already completed
|
||||||
try:
|
pid, status = os.waitpid(-1, os.WNOHANG)
|
||||||
# This will raise an exception on Windows. That's ok.
|
except OSError:
|
||||||
pid, status = os.waitpid(any_process, os.WNOHANG)
|
break
|
||||||
if pid == 0:
|
|
||||||
break
|
if pid == 0:
|
||||||
print("Warning -- reap_children() reaped child process %s"
|
break
|
||||||
% pid, file=sys.stderr)
|
|
||||||
except:
|
print("Warning -- reap_children() reaped child process %s"
|
||||||
break
|
% pid, file=sys.stderr)
|
||||||
|
environment_altered = True
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def start_threads(threads, unlock=None):
|
def start_threads(threads, unlock=None):
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
import contextlib
|
||||||
|
import errno
|
||||||
import importlib
|
import importlib
|
||||||
|
import io
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import os
|
|
||||||
import unittest
|
|
||||||
import socket
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import errno
|
import time
|
||||||
|
import unittest
|
||||||
from test import support
|
from test import support
|
||||||
|
|
||||||
TESTFN = support.TESTFN
|
TESTFN = support.TESTFN
|
||||||
|
@ -378,6 +381,51 @@ class TestSupport(unittest.TestCase):
|
||||||
|
|
||||||
self.assertRaises(AssertionError, support.check__all__, self, unittest)
|
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
|
# XXX -follows a list of untested API
|
||||||
# make_legacy_pyc
|
# make_legacy_pyc
|
||||||
# is_resource_enabled
|
# is_resource_enabled
|
||||||
|
@ -398,7 +446,6 @@ class TestSupport(unittest.TestCase):
|
||||||
# run_doctest
|
# run_doctest
|
||||||
# threading_cleanup
|
# threading_cleanup
|
||||||
# reap_threads
|
# reap_threads
|
||||||
# reap_children
|
|
||||||
# strip_python_stderr
|
# strip_python_stderr
|
||||||
# args_from_interpreter_flags
|
# args_from_interpreter_flags
|
||||||
# can_symlink
|
# can_symlink
|
||||||
|
|
Loading…
Reference in New Issue