Skip to content

Commit

Permalink
[3.12] gh-110205: Fix asyncio ThreadedChildWatcher._join_threads() (G…
Browse files Browse the repository at this point in the history
…H-110884) (#111412)

- `ThreadedChildWatcher.close()` is now *officially* a no-op; `_join_threads()` never did anything.
- Threads created by that class are now named `asyncio-waitpid-NNN`.
- `test.test_asyncio.utils.TestCase.close_loop()` now waits for the child watcher's threads, but not forever; if a thread hangs, it raises `RuntimeError`.
(cherry picked from commit c3bb10c)

Co-authored-by: Guido van Rossum <guido@python.org>
  • Loading branch information
miss-islington and gvanrossum committed Oct 27, 2023
1 parent 754fda8 commit 2398036
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 12 deletions.
11 changes: 2 additions & 9 deletions Lib/asyncio/unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1367,14 +1367,7 @@ def is_active(self):
return True

def close(self):
self._join_threads()

def _join_threads(self):
"""Internal: Join all non-daemon threads"""
threads = [thread for thread in list(self._threads.values())
if thread.is_alive() and not thread.daemon]
for thread in threads:
thread.join()
pass

def __enter__(self):
return self
Expand All @@ -1393,7 +1386,7 @@ def __del__(self, _warn=warnings.warn):
def add_child_handler(self, pid, callback, *args):
loop = events.get_running_loop()
thread = threading.Thread(target=self._do_waitpid,
name=f"waitpid-{next(self._pid_counter)}",
name=f"asyncio-waitpid-{next(self._pid_counter)}",
args=(loop, pid, callback, args),
daemon=True)
self._threads[pid] = thread
Expand Down
11 changes: 8 additions & 3 deletions Lib/test/test_asyncio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ def close_loop(loop):
else:
loop._default_executor.shutdown(wait=True)
loop.close()

policy = support.maybe_get_event_loop_policy()
if policy is not None:
try:
Expand All @@ -558,9 +559,13 @@ def close_loop(loop):
pass
else:
if isinstance(watcher, asyncio.ThreadedChildWatcher):
threads = list(watcher._threads.values())
for thread in threads:
thread.join()
# Wait for subprocess to finish, but not forever
for thread in list(watcher._threads.values()):
thread.join(timeout=support.SHORT_TIMEOUT)
if thread.is_alive():
raise RuntimeError(f"thread {thread} still alive: "
"subprocess still running")


def set_event_loop(self, loop, *, cleanup=True):
if loop is None:
Expand Down

0 comments on commit 2398036

Please sign in to comment.