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.

`