Commit Graph

3 Commits

Author SHA1 Message Date
XiaoZAZA a9a0648f49 fix(desktop): reap orphaned serve backends via parent-death watchdog + group-kill
An unclean desktop exit (crash / SIGKILL / update handoff) stranded every
`hermes serve` profile backend as an orphan (ppid=1) still serving, each
holding its MCP child subtree — 31 orphans / ~1.3 GiB RSS on one install.

Root causes + fixes:
- serve had no parent-death watchdog: add _start_parent_death_watchdog() in
  web_server.py (mirrors slash_worker.py), gated on HERMES_PARENT_PID; os._exit
  cascades to MCP watchdogs. No-op for standalone `hermes serve`.
- desktop passes HERMES_PARENT_PID in both serve spawn env blocks (main.ts).
- POSIX teardown now group-kills (process.kill(-pid, ...)) so MCP grandchildren
  die too (backend-child.ts + waitForBackendExit SIGKILL fallback).

Windows path unchanged (forceKillProcessTree). Tests updated + passing.
2026-08-10 17:02:56 -07:00
Teknium da3a0a852f fix(update): make orphan-backend reap tree-aware + drain Desktop update trees without pre-signalling
Follow-up to #82179 addressing helix4u's review comment
(#82179 issuecomment-5229441571). Three parts:

1. Desktop teardown (salvaged from #77436, @4adwentures): the update
   hand-off's releaseBackendLock() sent SIGTERM to the primary backend
   BEFORE taskkill /T. If the launcher exits first, Windows can no longer
   enumerate its descendants and they survive holding the venv — the
   Electron path that creates the orphan #82179 then has to repair.
   New stopBackendTreesForUpdate() tree-kills the live root first, with
   the behavioral vitest from #77436. The scanner half of #77436 is
   deliberately NOT taken (superseded by #82158's full-cmdline scan).

2. Tree-aware orphan classification: _orphaned_desktop_backend_pids()
   previously refused the whole holder set when any holder had a live
   parent. But the scanner legitimately returns an orphaned serve root
   AND its descendants (the venv trampoline's uv-managed interpreter
   worker — which carries the same backend argv — plus .hermes-runtime
   children). Those have a live parent: the orphan root itself. Now
   holders inside an accepted orphan root's tree fold into that root
   (only roots are returned; taskkill /T reaps descendants), and
   live-parent backends defer to the ancestry check instead of refusing
   outright. Anything outside an orphan tree still refuses.

3. Tests for the mixed shapes: root+managed-runtime child,
   grandchild depth, non-descendant stray alongside an orphan root
   (still refuses), descendant exited mid-classify.

E2E on a real Windows box: spawned a detached backend-shaped orphan
that itself spawned children (3 python descendants); the scanner-shaped
mixed holder set classified to [root], taskkill /T reaped root and all
descendants. The live Desktop backend on the box still classified None
(refusal preserved). The first E2E attempt caught exactly the
trampoline/worker case the mocks missed — the live worker re-execs with
the same backend argv and a live parent — which is what part 2 fixes.

Co-Authored-By: 4adwentures <296413879+4adwentures@users.noreply.github.com>
2026-08-08 20:01:03 -07:00
ethernet 4527943e91 test(desktop): extract hiddenWindowsChildOptions + stopBackendChild for real unit tests
windows-child-process.test.ts regexed main.ts and bootstrap-runner.ts
source text to check that spawn/execFileSync call sites wrapped their
options with hiddenWindowsChildOptions(), and that backend teardown
chose the right kill strategy.

Extract both into dependency-free sibling modules:
- windows-child-options.ts: hiddenWindowsChildOptions(options,
isWindows)
  now takes isWindows as an injectable param (defaults to the real
  platform check). main.ts and bootstrap-runner.ts both import the same
  implementation instead of each defining their own copy.
- backend-child.ts: stopBackendChild(child, deps) with
forceKillProcessTree
  and isWindows injected, so the SIGTERM-vs-tree-kill branching is
directly
  testable with a fake child + a spy.

windows-child-options.test.ts replaces windows-child-process.test.ts,
calling the real functions with fake spawn/execFileSync-shaped objects
and asserting on the actual returned options / kill call.
2026-07-13 17:22:17 -04:00