fix(shell): harden kill path and add zombie reap tests

Skip process.kill() when returncode is already set so generic exception
handlers after a successful communicate() cannot raise ProcessLookupError.
Suppress race kill failures and still run the safety-net reap.

Add unit and integration coverage for owned-PID reaping on normal exit,
timeout, exception, and exec-session kill/poll paths.
This commit is contained in:
Eric Yang
2026-07-10 20:19:44 +08:00
committed by Xubin Ren
parent a1fbfd9f7b
commit 9a1d1e64c7
2 changed files with 266 additions and 2 deletions
+11 -2
View File
@@ -632,9 +632,18 @@ class ExecTool(Tool):
@staticmethod
async def _kill_process(process: asyncio.subprocess.Process) -> None:
"""Kill a subprocess and reap it to prevent zombies."""
process.kill()
"""Kill a subprocess and reap it to prevent zombies.
Safe to call when the process has already exited (e.g. generic
exception handlers after a successful ``communicate()``): skips
``kill()`` and only runs the safety-net reap.
"""
if process.returncode is not None:
_reap_pid(process.pid)
return
try:
with suppress(ProcessLookupError):
process.kill()
with suppress(asyncio.TimeoutError):
await asyncio.wait_for(process.wait(), timeout=5.0)
finally: