fix(tui): use direct parent identity in slash worker

Remove process creation time and pid_exists from the slash worker parent-death predicate. The worker remains attached while its original PPID matches and keeps the existing in-flight grace behavior.\n\nRefs #62505
This commit is contained in:
Bruce-anle 2026-07-15 13:31:17 +08:00 committed by Teknium
parent 3d031bdb29
commit 68f7f15207
2 changed files with 20 additions and 32 deletions

View file

@ -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__

View file

@ -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()):