diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2019-05-14 05:12:49 -0700 |
---|---|---|
committer | Victor Stinner <vstinner@redhat.com> | 2019-05-14 14:12:49 +0200 |
commit | d8e123a48f1666227abdb90d84c58efe7bb4f3d8 (patch) | |
tree | 17b52a494950c5287a530d43c89452b807bc2680 /Lib | |
parent | 30cccf084d1560d9e3382e69d828b3be8cdb0286 (diff) | |
download | cpython-git-d8e123a48f1666227abdb90d84c58efe7bb4f3d8.tar.gz |
bpo-36719: Fix regrtest MultiprocessThread (GH-13301) (GH-13303)
MultiprocessThread.kill() now closes stdout and stderr to prevent
popen.communicate() to hang.
(cherry picked from commit c923c3449f825021b13521b2380e67ba35a36f55)
Co-authored-by: Victor Stinner <vstinner@redhat.com>
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/libregrtest/runtest_mp.py | 59 |
1 files changed, 55 insertions, 4 deletions
diff --git a/Lib/test/libregrtest/runtest_mp.py b/Lib/test/libregrtest/runtest_mp.py index ced7f866a8..42178471ef 100644 --- a/Lib/test/libregrtest/runtest_mp.py +++ b/Lib/test/libregrtest/runtest_mp.py @@ -21,6 +21,9 @@ from test.libregrtest.utils import format_duration # Display the running tests if nothing happened last N seconds PROGRESS_UPDATE = 30.0 # seconds +# Time to wait until a worker completes: should be immediate +JOIN_TIMEOUT = 30.0 # seconds + def must_stop(result, ns): if result.result == INTERRUPTED: @@ -91,6 +94,10 @@ class MultiprocessIterator: MultiprocessResult = collections.namedtuple('MultiprocessResult', 'result stdout stderr error_msg') +class ExitThread(Exception): + pass + + class MultiprocessThread(threading.Thread): def __init__(self, pending, output, ns): super().__init__() @@ -100,13 +107,31 @@ class MultiprocessThread(threading.Thread): self.current_test_name = None self.start_time = None self._popen = None + self._killed = False + + def __repr__(self): + info = ['MultiprocessThread'] + test = self.current_test_name + if self.is_alive(): + info.append('alive') + if test: + info.append(f'test={test}') + popen = self._popen + if popen: + info.append(f'pid={popen.pid}') + return '<%s>' % ' '.join(info) def kill(self): + self._killed = True + popen = self._popen if popen is None: return - print("Kill regrtest worker process %s" % popen.pid) popen.kill() + # stdout and stderr must be closed to ensure that communicate() + # does not hang + popen.stdout.close() + popen.stderr.close() def _runtest(self, test_name): try: @@ -117,7 +142,21 @@ class MultiprocessThread(threading.Thread): popen = self._popen with popen: try: - stdout, stderr = popen.communicate() + if self._killed: + # If kill() has been called before self._popen is set, + # self._popen is still running. Call again kill() + # to ensure that the process is killed. + self.kill() + raise ExitThread + + try: + stdout, stderr = popen.communicate() + except OSError: + if self._killed: + # kill() has been called: communicate() fails + # on reading closed stdout/stderr + raise ExitThread + raise except: self.kill() popen.wait() @@ -154,7 +193,7 @@ class MultiprocessThread(threading.Thread): return MultiprocessResult(result, stdout, stderr, err_msg) def run(self): - while True: + while not self._killed: try: try: test_name = next(self.pending) @@ -166,6 +205,8 @@ class MultiprocessThread(threading.Thread): if must_stop(mp_result.result, self.ns): break + except ExitThread: + break except BaseException: self.output.put((True, traceback.format_exc())) break @@ -205,10 +246,20 @@ class MultiprocessRunner: worker.start() def wait_workers(self): + start_time = time.monotonic() for worker in self.workers: worker.kill() for worker in self.workers: - worker.join() + while True: + worker.join(1.0) + if not worker.is_alive(): + break + dt = time.monotonic() - start_time + print("Wait for regrtest worker %r for %.1f sec" % (worker, dt)) + if dt > JOIN_TIMEOUT: + print("Warning -- failed to join a regrtest worker %s" + % worker) + break def _get_result(self): if not any(worker.is_alive() for worker in self.workers): |