@@ -808,7 +808,8 @@ def _send_interupt_children(self):
808
808
pid = os .getpid ()
809
809
pgid = os .getpgid (pid )
810
810
# 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" ):
812
813
try :
813
814
os .killpg (pgid , SIGINT )
814
815
return
@@ -1136,67 +1137,73 @@ def _input_request(self, prompt, ident, parent, password=False):
1136
1137
raise EOFError
1137
1138
return value
1138
1139
1139
- def _killpg (self , signal ):
1140
+ def _signal_children (self , signum ):
1140
1141
"""
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
1143
1143
1144
+ Like `killpg`, but does not include the current process
1145
+ (or possible parents).
1144
1146
"""
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" )
1151
1149
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
1162
1150
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
1164
1187
1165
- pgid = os . getpgid ( os . getpid ())
1188
+ async def _progressively_terminate_all_children ( self ):
1166
1189
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
1168
1191
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
1176
1193
1177
1194
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 ():
1180
1196
self .log .debug ("Kernel has no children." )
1181
1197
return
1182
- self .log .debug (f"Trying to interrupt then kill subprocesses : { children } " )
1183
- self ._send_interupt_children ()
1184
1198
1185
1199
for signum in (SIGTERM , SIGKILL ):
1186
- self .log .debug (
1187
- f"Will try to send { signum } ({ Signals (signum )!r} ) to subprocesses :{ children } "
1188
- )
1189
1200
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 )
1200
1207
self .log .debug (
1201
1208
f"Will sleep { delay } s before checking for children and retrying. { children } "
1202
1209
)
0 commit comments