mirror of
https://github.com/python/cpython.git
synced 2025-10-03 13:45:29 +00:00
closes bpo-38692: Add a pidfd child process watcher to asyncio. (GH-17069)
This commit is contained in:
parent
dad6be5ffe
commit
3ccdd9b180
5 changed files with 102 additions and 0 deletions
|
@ -257,6 +257,18 @@ implementation used by the asyncio event loop:
|
||||||
This solution requires a running event loop in the main thread to work, as
|
This solution requires a running event loop in the main thread to work, as
|
||||||
:class:`SafeChildWatcher`.
|
:class:`SafeChildWatcher`.
|
||||||
|
|
||||||
|
.. class:: PidfdChildWatcher
|
||||||
|
|
||||||
|
This implementation polls process file descriptors (pidfds) to await child
|
||||||
|
process termination. In some respects, :class:`PidfdChildWatcher` is a
|
||||||
|
"Goldilocks" child watcher implementation. It doesn't require signals or
|
||||||
|
threads, doesn't interfere with any processes launched outside the event
|
||||||
|
loop, and scales linearly with the number of subprocesses launched by the
|
||||||
|
event loop. The main disadvantage is that pidfds are specific to Linux, and
|
||||||
|
only work on recent (5.3+) kernels.
|
||||||
|
|
||||||
|
.. versionadded:: 3.9
|
||||||
|
|
||||||
|
|
||||||
Custom Policies
|
Custom Policies
|
||||||
===============
|
===============
|
||||||
|
|
|
@ -130,6 +130,9 @@ that schedules a shutdown for the default executor that waits on the
|
||||||
:func:`asyncio.run` has been updated to use the new :term:`coroutine`.
|
:func:`asyncio.run` has been updated to use the new :term:`coroutine`.
|
||||||
(Contributed by Kyle Stanley in :issue:`34037`.)
|
(Contributed by Kyle Stanley in :issue:`34037`.)
|
||||||
|
|
||||||
|
Added :class:`asyncio.PidfdChildWatcher`, a Linux-specific child watcher
|
||||||
|
implementation that polls process file descriptors. (:issue:`38692`)
|
||||||
|
|
||||||
curses
|
curses
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -878,6 +878,73 @@ class AbstractChildWatcher:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class PidfdChildWatcher(AbstractChildWatcher):
|
||||||
|
"""Child watcher implementation using Linux's pid file descriptors.
|
||||||
|
|
||||||
|
This child watcher polls process file descriptors (pidfds) to await child
|
||||||
|
process termination. In some respects, PidfdChildWatcher is a "Goldilocks"
|
||||||
|
child watcher implementation. It doesn't require signals or threads, doesn't
|
||||||
|
interfere with any processes launched outside the event loop, and scales
|
||||||
|
linearly with the number of subprocesses launched by the event loop. The
|
||||||
|
main disadvantage is that pidfds are specific to Linux, and only work on
|
||||||
|
recent (5.3+) kernels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._loop = None
|
||||||
|
self._callbacks = {}
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return self._loop is not None and self._loop.is_running()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.attach_loop(None)
|
||||||
|
|
||||||
|
def attach_loop(self, loop):
|
||||||
|
if self._loop is not None and loop is None and self._callbacks:
|
||||||
|
warnings.warn(
|
||||||
|
'A loop is being detached '
|
||||||
|
'from a child watcher with pending handlers',
|
||||||
|
RuntimeWarning)
|
||||||
|
for pidfd, _, _ in self._callbacks.values():
|
||||||
|
self._loop._remove_reader(pidfd)
|
||||||
|
os.close(pidfd)
|
||||||
|
self._callbacks.clear()
|
||||||
|
self._loop = loop
|
||||||
|
|
||||||
|
def add_child_handler(self, pid, callback, *args):
|
||||||
|
existing = self._callbacks.get(pid)
|
||||||
|
if existing is not None:
|
||||||
|
self._callbacks[pid] = existing[0], callback, args
|
||||||
|
else:
|
||||||
|
pidfd = os.pidfd_open(pid)
|
||||||
|
self._loop._add_reader(pidfd, self._do_wait, pid)
|
||||||
|
self._callbacks[pid] = pidfd, callback, args
|
||||||
|
|
||||||
|
def _do_wait(self, pid):
|
||||||
|
pidfd, callback, args = self._callbacks.pop(pid)
|
||||||
|
self._loop._remove_reader(pidfd)
|
||||||
|
_, status = os.waitpid(pid, 0)
|
||||||
|
os.close(pidfd)
|
||||||
|
returncode = _compute_returncode(status)
|
||||||
|
callback(pid, returncode, *args)
|
||||||
|
|
||||||
|
def remove_child_handler(self, pid):
|
||||||
|
try:
|
||||||
|
pidfd, _, _ = self._callbacks.pop(pid)
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
self._loop._remove_reader(pidfd)
|
||||||
|
os.close(pidfd)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _compute_returncode(status):
|
def _compute_returncode(status):
|
||||||
if os.WIFSIGNALED(status):
|
if os.WIFSIGNALED(status):
|
||||||
# The child process died because of a signal.
|
# The child process died because of a signal.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -691,6 +692,23 @@ if sys.platform != 'win32':
|
||||||
|
|
||||||
Watcher = unix_events.FastChildWatcher
|
Watcher = unix_events.FastChildWatcher
|
||||||
|
|
||||||
|
def has_pidfd_support():
|
||||||
|
if not hasattr(os, 'pidfd_open'):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
os.close(os.pidfd_open(os.getpid()))
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@unittest.skipUnless(
|
||||||
|
has_pidfd_support(),
|
||||||
|
"operating system does not support pidfds",
|
||||||
|
)
|
||||||
|
class SubprocessPidfdWatcherTests(SubprocessWatcherMixin,
|
||||||
|
test_utils.TestCase):
|
||||||
|
Watcher = unix_events.PidfdChildWatcher
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Windows
|
# Windows
|
||||||
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):
|
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :class:`asyncio.PidfdChildWatcher`, a Linux-specific child watcher
|
||||||
|
implementation that polls process file descriptors.
|
Loading…
Add table
Add a link
Reference in a new issue