Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
57 changes: 52 additions & 5 deletions Lib/test/test_support.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down