closes bpo-38692: Add a pidfd child process watcher to asyncio. (GH-1… · python/cpython@3ccdd9b (original) (raw)
`@@ -878,6 +878,73 @@ def exit(self, a, b, c):
`
878
878
`raise NotImplementedError()
`
879
879
``
880
880
``
``
881
`+
class PidfdChildWatcher(AbstractChildWatcher):
`
``
882
`+
"""Child watcher implementation using Linux's pid file descriptors.
`
``
883
+
``
884
`+
This child watcher polls process file descriptors (pidfds) to await child
`
``
885
`+
process termination. In some respects, PidfdChildWatcher is a "Goldilocks"
`
``
886
`+
child watcher implementation. It doesn't require signals or threads, doesn't
`
``
887
`+
interfere with any processes launched outside the event loop, and scales
`
``
888
`+
linearly with the number of subprocesses launched by the event loop. The
`
``
889
`+
main disadvantage is that pidfds are specific to Linux, and only work on
`
``
890
`+
recent (5.3+) kernels.
`
``
891
`+
"""
`
``
892
+
``
893
`+
def init(self):
`
``
894
`+
self._loop = None
`
``
895
`+
self._callbacks = {}
`
``
896
+
``
897
`+
def enter(self):
`
``
898
`+
return self
`
``
899
+
``
900
`+
def exit(self, exc_type, exc_value, exc_traceback):
`
``
901
`+
pass
`
``
902
+
``
903
`+
def is_active(self):
`
``
904
`+
return self._loop is not None and self._loop.is_running()
`
``
905
+
``
906
`+
def close(self):
`
``
907
`+
self.attach_loop(None)
`
``
908
+
``
909
`+
def attach_loop(self, loop):
`
``
910
`+
if self._loop is not None and loop is None and self._callbacks:
`
``
911
`+
warnings.warn(
`
``
912
`+
'A loop is being detached '
`
``
913
`+
'from a child watcher with pending handlers',
`
``
914
`+
RuntimeWarning)
`
``
915
`+
for pidfd, _, _ in self._callbacks.values():
`
``
916
`+
self._loop._remove_reader(pidfd)
`
``
917
`+
os.close(pidfd)
`
``
918
`+
self._callbacks.clear()
`
``
919
`+
self._loop = loop
`
``
920
+
``
921
`+
def add_child_handler(self, pid, callback, *args):
`
``
922
`+
existing = self._callbacks.get(pid)
`
``
923
`+
if existing is not None:
`
``
924
`+
self._callbacks[pid] = existing[0], callback, args
`
``
925
`+
else:
`
``
926
`+
pidfd = os.pidfd_open(pid)
`
``
927
`+
self._loop._add_reader(pidfd, self._do_wait, pid)
`
``
928
`+
self._callbacks[pid] = pidfd, callback, args
`
``
929
+
``
930
`+
def _do_wait(self, pid):
`
``
931
`+
pidfd, callback, args = self._callbacks.pop(pid)
`
``
932
`+
self._loop._remove_reader(pidfd)
`
``
933
`+
_, status = os.waitpid(pid, 0)
`
``
934
`+
os.close(pidfd)
`
``
935
`+
returncode = _compute_returncode(status)
`
``
936
`+
callback(pid, returncode, *args)
`
``
937
+
``
938
`+
def remove_child_handler(self, pid):
`
``
939
`+
try:
`
``
940
`+
pidfd, _, _ = self._callbacks.pop(pid)
`
``
941
`+
except KeyError:
`
``
942
`+
return False
`
``
943
`+
self._loop._remove_reader(pidfd)
`
``
944
`+
os.close(pidfd)
`
``
945
`+
return True
`
``
946
+
``
947
+
881
948
`def _compute_returncode(status):
`
882
949
`if os.WIFSIGNALED(status):
`
883
950
`# The child process died because of a signal.
`