diff --git a/tests/test_slash_worker_watchdog.py b/tests/test_slash_worker_watchdog.py index 198524c522d..340dd56c738 100644 --- a/tests/test_slash_worker_watchdog.py +++ b/tests/test_slash_worker_watchdog.py @@ -1,21 +1,24 @@ -import psutil +import inspect from tui_gateway import slash_worker def test_is_orphaned_true_when_ppid_changes(): # Our parent went away and we were reparented to a subreaper/init. - assert slash_worker._is_orphaned(1234, 1.0, getppid=lambda: 999999) is True + assert slash_worker._is_orphaned(1234, getppid=lambda: 999999) is True -def test_is_orphaned_true_when_parent_create_time_mismatch(): - # Same ppid but a different create_time means the PID was reused. - me = psutil.Process() - assert slash_worker._is_orphaned(me.pid, 0.0, getppid=lambda: me.pid) is True +def test_is_orphaned_false_when_direct_parent_is_unchanged(): + original_ppid = 1234 + assert slash_worker._is_orphaned(original_ppid, getppid=lambda: original_ppid) is False -def test_is_orphaned_false_when_parent_alive_and_matches(): - me = psutil.Process() - assert ( - slash_worker._is_orphaned(me.pid, me.create_time(), getppid=lambda: me.pid) is False - ) +def test_parent_death_watchdog_contract_has_no_create_time_plumbing(): + assert list(inspect.signature(slash_worker._is_orphaned).parameters) == [ + "original_ppid", + "getppid", + ] + assert list(inspect.signature(slash_worker._start_parent_death_watchdog).parameters) == [ + "original_ppid", + ] + assert "psutil" not in slash_worker.__dict__ diff --git a/tui_gateway/slash_worker.py b/tui_gateway/slash_worker.py index 34cc7595373..78905a5637a 100644 --- a/tui_gateway/slash_worker.py +++ b/tui_gateway/slash_worker.py @@ -25,8 +25,6 @@ import sys import threading import time -import psutil - import cli as cli_mod from cli import HermesCLI from rich.console import Console @@ -51,18 +49,9 @@ _ORPHAN_GRACE_S = max(0.0, _env_float("HERMES_SLASH_WATCHDOG_GRACE_S", 5.0)) _in_flight = threading.Event() # set while a command is executing -def _is_orphaned(original_ppid, parent_create_time, getppid=os.getppid) -> bool: - """True once our spawning gateway is gone. Compare to the ORIGINAL ppid - (never ==1: Linux reparents to a subreaper) and guard PID reuse via - create_time.""" - if getppid() != original_ppid: - return True - try: - if not psutil.pid_exists(original_ppid): - return True - return psutil.Process(original_ppid).create_time() != parent_create_time - except psutil.Error: - return True +def _is_orphaned(original_ppid, getppid=os.getppid) -> bool: + """Return whether this worker no longer has its original POSIX parent.""" + return getppid() != original_ppid def _prepare_slash_worker_runtime() -> None: @@ -86,9 +75,9 @@ def _prepare_slash_worker_runtime() -> None: wait_for_mcp_discovery() -def _start_parent_death_watchdog(original_ppid, parent_create_time) -> None: +def _start_parent_death_watchdog(original_ppid) -> None: def _loop(): - while not _is_orphaned(original_ppid, parent_create_time): + while not _is_orphaned(original_ppid): time.sleep(_WATCHDOG_POLL_S) deadline = time.monotonic() + _ORPHAN_GRACE_S while _in_flight.is_set() and time.monotonic() < deadline: @@ -145,11 +134,7 @@ def main(): # Start before the (hundreds-of-ms) HermesCLI build — that window is itself # an orphan risk if the gateway dies mid-spawn. orig_ppid = os.getppid() - try: - parent_create_time = psutil.Process(orig_ppid).create_time() - except psutil.Error: - parent_create_time = 0.0 - _start_parent_death_watchdog(orig_ppid, parent_create_time) + _start_parent_death_watchdog(orig_ppid) _prepare_slash_worker_runtime() with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):