@@ -808,7 +808,8 @@ def _send_interupt_children(self):
808808 pid = os .getpid ()
809809 pgid = os .getpgid (pid )
810810 # Prefer process-group over process
811- if pgid and hasattr (os , "killpg" ):
811+ # but only if the kernel is the leader of the process group
812+ if pgid and pgid == pid and hasattr (os , "killpg" ):
812813 try :
813814 os .killpg (pgid , SIGINT )
814815 return
@@ -1136,67 +1137,73 @@ def _input_request(self, prompt, ident, parent, password=False):
11361137 raise EOFError
11371138 return value
11381139
1139- def _killpg (self , signal ):
1140+ def _signal_children (self , signum ):
11401141 """
1141- similar to killpg but use psutil if it can on windows
1142- or if pgid is none
1142+ Send a signal to all our children
11431143
1144+ Like `killpg`, but does not include the current process
1145+ (or possible parents).
11441146 """
1145- pgid = os .getpgid (os .getpid ())
1146- if pgid and hasattr (os , "killpg" ):
1147- try :
1148- os .killpg (pgid , signal )
1149- except (OSError ) as e :
1150- self .log .exception (f"OSError running killpg, not killing children." )
1147+ if psutil is None :
1148+ self .log .info ("Need psutil to signal children" )
11511149 return
1152- elif psutil is not None :
1153- children = parent .children (recursive = True )
1154- for p in children :
1155- try :
1156- if signal == SIGTERM :
1157- p .terminate ()
1158- elif signal == SIGKILL :
1159- p .kill ()
1160- except psutil .NoSuchProcess :
1161- pass
11621150
1163- async def _progressively_terminate_all_children (self ):
1151+ for p in self ._process_children ():
1152+ self .log .debug (f"Sending { Signals (signum )!r} to subprocess { p } " )
1153+ try :
1154+ if signum == SIGTERM :
1155+ p .terminate ()
1156+ elif signum == SIGKILL :
1157+ p .kill ()
1158+ else :
1159+ p .send_signal (signum )
1160+ except psutil .NoSuchProcess :
1161+ pass
1162+
1163+ def _process_children (self ):
1164+ """Retrieve child processes in the kernel's process group
1165+
1166+ Avoids:
1167+ - including parents and self with killpg
1168+ - including all children that may have forked-off a new group
1169+ """
1170+ if psutil is None :
1171+ return []
1172+ kernel_process = psutil .Process ()
1173+ all_children = kernel_process .children (recursive = True )
1174+ if os .name == "nt" :
1175+ return all_children
1176+ kernel_pgid = os .getpgrp ()
1177+ process_group_children = []
1178+ for child in all_children :
1179+ try :
1180+ child_pgid = os .getpgid (child .pid )
1181+ except OSError :
1182+ pass
1183+ else :
1184+ if child_pgid == kernel_pgid :
1185+ process_group_children .append (child )
1186+ return process_group_children
11641187
1165- pgid = os . getpgid ( os . getpid ())
1188+ async def _progressively_terminate_all_children ( self ):
11661189 if psutil is None :
1167- # blindly send quickly sigterm/sigkill to processes if psutil not there.
1190+ # we need psutil to safely clean up children
11681191 self .log .info ("Please install psutil for a cleaner subprocess shutdown." )
1169- self ._send_interupt_children ()
1170- await asyncio .sleep (0.05 )
1171- self .log .debug ("Sending SIGTERM to {pgid}" )
1172- self ._killpg (SIGTERM )
1173- await asyncio .sleep (0.05 )
1174- self .log .debug ("Sending SIGKILL to {pgid}" )
1175- self ._killpg (pgid , SIGKILL )
1192+ return
11761193
11771194 sleeps = (0.01 , 0.03 , 0.1 , 0.3 , 1 , 3 , 10 )
1178- children = psutil .Process ().children (recursive = True )
1179- if not children :
1195+ if not self ._process_children ():
11801196 self .log .debug ("Kernel has no children." )
11811197 return
1182- self .log .debug (f"Trying to interrupt then kill subprocesses : { children } " )
1183- self ._send_interupt_children ()
11841198
11851199 for signum in (SIGTERM , SIGKILL ):
1186- self .log .debug (
1187- f"Will try to send { signum } ({ Signals (signum )!r} ) to subprocesses :{ children } "
1188- )
11891200 for delay in sleeps :
1190- children = psutil .Process ().children (recursive = True )
1191- try :
1192- if not children :
1193- self .log .warning (
1194- "No more children, continuing shutdown routine."
1195- )
1196- return
1197- except psutil .NoSuchProcess :
1198- pass
1199- self ._killpg (15 )
1201+ children = self ._process_children ()
1202+ if not children :
1203+ self .log .debug ("No more children, continuing shutdown routine." )
1204+ return
1205+ # signals only children, not current process
1206+ self ._signal_children (signum )
12001207 self .log .debug (
12011208 f"Will sleep { delay } s before checking for children and retrying. { children } "
12021209 )
0 commit comments