closes bpo-38692: Add a pidfd child process watcher to asyncio. (GH-17069)
This commit is contained in:
parent
dad6be5ffe
commit
3ccdd9b180
|
@ -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
|
||||
: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
|
||||
===============
|
||||
|
|
|
@ -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`.
|
||||
(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
|
||||
------
|
||||
|
||||
|
|
|
@ -878,6 +878,73 @@ class AbstractChildWatcher:
|
|||
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):
|
||||
if os.WIFSIGNALED(status):
|
||||
# The child process died because of a signal.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import signal
|
||||
import sys
|
||||
import unittest
|
||||
|
@ -691,6 +692,23 @@ if sys.platform != 'win32':
|
|||
|
||||
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:
|
||||
# Windows
|
||||
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…
Reference in New Issue