1919except ImportError :
2020COVERAGE_SIGNAL = None
2121
22- from psutil import AccessDenied ,NoSuchProcess ,Process ,TimeoutExpired ,wait_procs
22+ from psutil import (
23+ STATUS_ZOMBIE ,
24+ AccessDenied ,
25+ NoSuchProcess ,
26+ Process ,
27+ TimeoutExpired ,
28+ wait_procs ,
29+ )
2330
2431from .exceptions import TerminateError
2532
3037LOG = getLogger (__name__ )
3138
3239
40+ def _filter_zombies (procs :Iterable [Process ])-> Generator [Process ]:
41+ """Filter out zombie processes from a collection of processes.
42+
43+ Args:
44+ procs: Processes to check.
45+
46+ Yields:
47+ Processes that are not zombies.
48+ """
49+ for proc in procs :
50+ with suppress (AccessDenied ,NoSuchProcess ):
51+ if proc .status ()== STATUS_ZOMBIE :
52+ LOG .debug ("filtering zombie: %d - %s" ,proc .pid ,proc .name ())
53+ continue
54+ yield proc
55+
56+
3357def _last_modified (scan_dir :Path )-> float | None :
3458"""Scan directory recursively and find the latest modified date of all .gcda files.
3559
@@ -88,11 +112,11 @@ def _safe_wait_procs(
88112return (gone ,alive )
89113
90114
91- def _writing_coverage (procs :list [Process ])-> bool :
115+ def _writing_coverage (procs :Iterable [Process ])-> bool :
92116"""Check if any processes have open .gcda files.
93117
94118 Args:
95- procs:List of processes to check.
119+ procs:Processes to check.
96120
97121 Returns:
98122 True if processes with open .gcda files are found.
@@ -278,7 +302,7 @@ def processes(self, recursive: bool = False) -> list[Process]:
278302"""Processes in the process tree.
279303
280304 Args:
281- recursive:if False only the parent and child processes are returned.
305+ recursive:If False only the parent and child processes are returned.
282306
283307 Returns:
284308 Processes in the process tree.
@@ -312,7 +336,7 @@ def terminate(self) -> None:
312336LOG .debug ("attempting to terminate parent (%d)" ,self .parent .pid )
313337self .parent .terminate ()
314338self .parent .wait (timeout = 10 )
315- procs = _safe_wait_procs (procs ,timeout = 0 )[1 ]
339+ procs = list ( _filter_zombies ( _safe_wait_procs (procs ,timeout = 0 )[1 ]))
316340
317341use_kill = False
318342while procs :
@@ -329,7 +353,7 @@ def terminate(self) -> None:
329353else :
330354proc .terminate ()
331355# wait for processes to terminate
332- procs = _safe_wait_procs (procs ,timeout = 30 )[1 ]
356+ procs = list ( _filter_zombies ( _safe_wait_procs (procs ,timeout = 30 )[1 ]))
333357if use_kill :
334358break
335359use_kill = True
@@ -350,12 +374,9 @@ def wait(self, timeout: int = 300) -> int:
350374 Returns:
351375 Process exit code.
352376 """
353- try :
354- exit_code = self .parent .wait (timeout = timeout )or 0
355- except (AccessDenied ,NoSuchProcess ):# pragma: no cover
356- # this is triggered sometimes when the process goes away
357- exit_code = 0
358- return exit_code
377+ with suppress (AccessDenied ,NoSuchProcess ):
378+ return self .parent .wait (timeout = timeout )or 0
379+ return 0 # pragma: no cover
359380
360381def wait_procs (self ,timeout :float | None = 0 )-> int :
361382"""Wait for process tree to exit.