11
11
__date__ = "2023-11-15"
12
12
13
13
import logging
14
+ import contextlib
14
15
import multiprocessing .synchronize
15
16
import sys
16
17
import multiprocessing
@@ -205,6 +206,24 @@ def callback_function(status_code: int, std_out: str, std_err: str) -> None:
205
206
name = f"{ identifier } _exit_status" , source = "user"
206
207
)
207
208
209
+ @property
210
+ def processes (self ) -> list [psutil .Process ]:
211
+ """Create an array containing a list of processes"""
212
+ if not self ._processes :
213
+ return []
214
+
215
+ _all_processes : list [psutil .Process ] = [
216
+ psutil .Process (process .pid ) for process in self ._processes .values ()
217
+ ]
218
+
219
+ with contextlib .suppress (psutil .NoSuchProcess , psutil .ZombieProcess ):
220
+ for process in _all_processes :
221
+ for child in process .children (recursive = True ):
222
+ if child not in _all_processes :
223
+ _all_processes .append (child )
224
+
225
+ return list (set (_all_processes ))
226
+
208
227
@property
209
228
def success (self ) -> int :
210
229
"""Return whether all attached processes completed successfully"""
@@ -294,15 +313,31 @@ def _save_output(self) -> None:
294
313
f"{ self ._runner .name } _{ proc_id } .out" , category = "output"
295
314
)
296
315
297
- def kill_process (self , process_id : str ) -> None :
298
- """Kill a running process by ID"""
299
- if not (process := self ._processes .get (process_id )):
300
- logger .error (
301
- f"Failed to terminate process '{ process_id } ', no such identifier."
302
- )
303
- return
316
+ def kill_process (
317
+ self , process_id : typing .Union [int , str ], kill_children_only : bool = False
318
+ ) -> None :
319
+ """Kill a running process by ID
304
320
305
- parent = psutil .Process (process .pid )
321
+ If argument is a string this is a process handled by the client,
322
+ else it is a PID of a external monitored process
323
+
324
+ Parameters
325
+ ----------
326
+ process_id : typing.Union[int, str]
327
+ either the identifier for a client created process or the PID
328
+ of an external process
329
+ kill_children_only : bool, optional
330
+ if process_id is an integer, whether to kill only its children
331
+ """
332
+ if isinstance (process_id , str ):
333
+ if not (process := self ._processes .get (process_id )):
334
+ logger .error (
335
+ f"Failed to terminate process '{ process_id } ', no such identifier."
336
+ )
337
+ return
338
+ parent = psutil .Process (process .pid )
339
+ elif isinstance (process_id , int ):
340
+ parent = psutil .Process (process_id )
306
341
307
342
for child in parent .children (recursive = True ):
308
343
logger .debug (f"Terminating child process { child .pid } : { child .name ()} " )
@@ -311,11 +346,13 @@ def kill_process(self, process_id: str) -> None:
311
346
for child in parent .children (recursive = True ):
312
347
child .wait ()
313
348
314
- logger .debug (f"Terminating child process { process .pid } : { process .args } " )
315
- process .kill ()
316
- process .wait ()
349
+ logger .debug (f"Terminating process { process .pid } : { process .args } " )
350
+ if not kill_children_only :
351
+ process .kill ()
352
+ process .wait ()
317
353
318
- self ._execute_callback (process_id )
354
+ if isinstance (process_id , str ):
355
+ self ._execute_callback (process_id )
319
356
320
357
def kill_all (self ) -> None :
321
358
"""Kill all running processes"""
0 commit comments