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