Commit Graph
9 Commits
Author SHA1 Message Date
Eric YangandXubin Ren 9a1d1e64c7 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.
2026-07-10 20:19:44 +08:00
Eric YangandXubin Ren a1fbfd9f7b fix(shell): drop gateway-wide waitpid(-1) zombie reaper
A global reaper can race asyncio's child watcher for just-exited
create_subprocess_exec children, causing Process returncode 255 instead
of the real command status. Keep only owned-PID reaps after wait/communicate
/kill paths in shell and exec_session.

Addresses review feedback on #4840.
2026-07-10 20:19:44 +08:00
Eric YangandXubin Ren bda0c099ab fix(shell): guard _reap_pid on os.waitpid availability
Windows CI runs Unix-path unit tests by patching _IS_WINDOWS=False while
still on win32, where os.WNOHANG/os.waitpid do not exist. Use capability
checks so reaping is a no-op on platforms without waitpid rather than
trusting the (mockable) platform flag.
2026-07-10 20:19:44 +08:00
Eric YangandXubin Ren ef14d1ea92 fix(shell): remove unused os import in exec_session
Ruff F401 failed CI on the zombie-reap PR; reaping uses shell._reap_pid
so os is not needed in this module.
2026-07-10 20:19:44 +08:00
Eric YangandXubin Ren c9e014fdea fix(shell): reap zombie processes on all subprocess exit paths
The previous fix (dbcc7cb5) only added os.waitpid() to _kill_process(),
covering the timeout/cancel path of one-shot exec. Zombies continued to
accumulate because several other exit paths never reaped children:

- _ExecSession.kill(): sent SIGKILL + process.wait(5s) but had no
  os.waitpid() fallback if the wait timed out
- ExecTool.execute() generic exception handler: leaked the subprocess
  if communicate() raised an unexpected error
- Normal completion paths: relied entirely on asyncio's child-watcher,
  which can miss exits inside Docker containers (pidfd/SIGCHLD gaps)

Changes:
- Extract _reap_pid() helper for consistent, safe os.waitpid(WNOHANG)
- Add _reap_pid() fallback to _ExecSession.kill() via try/finally
- Add _reap_pid() safety-net after normal process exit in both
  ExecTool.execute() and _ExecSession.poll()
- Kill + reap subprocess in the generic except Exception handler
- Add periodic zombie reaper background task (every 30s) in the
  gateway as a last line of defense
2026-07-10 20:19:44 +08:00
Eric YangandXubin Ren 84a7f8af73 refactor(shell): fix syntax error 2026-03-24 00:02:49 +08:00
Eric YangandXubin Ren e2e1c9c276 refactor(shell): use finally block to reap zombie processes on timeoutx 2026-03-24 00:02:49 +08:00
Eric YangandXubin Ren dbcc7cb539 refactor(shell): use finally block to reap zombie processes on timeout 2026-03-24 00:02:49 +08:00
Eric YangandXubin Ren e423ceef9c fix(shell): reap zombie processes when command timeout kills subprocess 2026-03-24 00:02:49 +08:00