closes bpo-38692: Add a pidfd child process watcher to asyncio. (GH-17069)

This commit is contained in:
Benjamin Peterson 2019-11-13 19:08:50 -08:00 committed by GitHub
parent dad6be5ffe
commit 3ccdd9b180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 0 deletions

View File

@ -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
===============

View File

@ -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
------

View File

@ -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.

View File

@ -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):

View File

@ -0,0 +1,2 @@
Add :class:`asyncio.PidfdChildWatcher`, a Linux-specific child watcher
implementation that polls process file descriptors.