@@ -218,7 +218,7 @@ async def _make_subprocess_transport(self, protocol, args, shell,
218218 return transp
219219
220220 def _child_watcher_callback (self , pid , returncode , transp ):
221- self . call_soon_threadsafe ( transp ._process_exited , returncode )
221+ transp ._process_exited ( returncode )
222222
223223 async def create_unix_connection (
224224 self , protocol_factory , path = None , * ,
@@ -928,6 +928,49 @@ def add_child_handler(self, pid, callback, *args):
928928 def _do_waitpid (self , loop , expected_pid , callback , args ):
929929 assert expected_pid > 0
930930
931+ if hasattr (os , 'waitid' ):
932+ # Wait for the child process using waitid() on platforms which support it.
933+ # WNOWAIT is used to avoid reaping the child process, allowing the event loop to
934+ # reap the child process with waitpid() later in event loop thread.
935+ # This makes the reaping of the child and notification of the return code
936+ # atomic with respect to the event loop thread.
937+ try :
938+ os .waitid (os .P_PID , expected_pid , os .WEXITED | os .WNOWAIT )
939+ except ChildProcessError :
940+ # The child process is already reaped
941+ pass
942+ if loop .is_closed ():
943+ # loop is already closed, reap the zombie here so that it is not leaked.
944+ pid , _ = self ._reap (loop , expected_pid )
945+ logger .warning ("Loop %r that handles pid %r is closed" ,
946+ loop , pid )
947+ else :
948+ try :
949+ loop .call_soon_threadsafe (
950+ self ._reap_and_notify , loop , expected_pid ,
951+ callback , args )
952+ except RuntimeError :
953+ # The event loop was closed concurrently.
954+ pid , _ = self ._reap (loop , expected_pid )
955+ logger .warning ("Loop %r that handles pid %r is closed" ,
956+ loop , pid )
957+ else :
958+ # Fallback for platforms that don't support waitid(): we have to
959+ # reap the child here, which is racy with respect to send_signal()
960+ pid , returncode = self ._reap (loop , expected_pid )
961+ if loop .is_closed ():
962+ logger .warning ("Loop %r that handles pid %r is closed" ,
963+ loop , pid )
964+ else :
965+ loop .call_soon_threadsafe (callback , pid , returncode , * args )
966+
967+ self ._threads .pop (expected_pid )
968+
969+ def _reap_and_notify (self , loop , expected_pid , callback , args ):
970+ pid , returncode = self ._reap (loop , expected_pid )
971+ callback (pid , returncode , * args )
972+
973+ def _reap (self , loop , expected_pid ):
931974 try :
932975 pid , status = os .waitpid (expected_pid , 0 )
933976 except ChildProcessError :
@@ -943,13 +986,7 @@ def _do_waitpid(self, loop, expected_pid, callback, args):
943986 if loop .get_debug ():
944987 logger .debug ('process %s exited with returncode %s' ,
945988 expected_pid , returncode )
946-
947- if loop .is_closed ():
948- logger .warning ("Loop %r that handles pid %r is closed" , loop , pid )
949- else :
950- loop .call_soon_threadsafe (callback , pid , returncode , * args )
951-
952- self ._threads .pop (expected_pid )
989+ return pid , returncode
953990
954991def can_use_pidfd ():
955992 if not hasattr (os , 'pidfd_open' ):
0 commit comments