Skip to content

fix(gateway): reap cgroup orphans via ExecStopPost to unblock systemd restart - #54090

Merged
teknium1 merged 3 commits into
mainfrom
hermes/hermes-2b22bdad
Jun 28, 2026
Merged

fix(gateway): reap cgroup orphans via ExecStopPost to unblock systemd restart#54090
teknium1 merged 3 commits into
mainfrom
hermes/hermes-2b22bdad

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

A long-lived helper left in the gateway's systemd cgroup after the main process exits no longer blocks Restart=always — a new ExecStopPost= reaper SIGKILLs whatever the gateway didn't clean up itself, so the unit can restart immediately instead of stalling for minutes.

Root cause (#37454): KillMode=mixed only signals the main PID. On shutdown an untracked helper (e.g. adb) survived in the cgroup; systemd's cgroup-wide kill returned EINVAL, leaving the process parked in the cgroup and Restart=always unable to fire for 6+ minutes — a full outage across every platform and all cron jobs.

Changes

  • gateway/cgroup_cleanup.py (new): reads the unit's own cgroup.procs and SIGKILLs each remaining PID individually (skipping itself), tolerating already-exited / unkillable PIDs. Deliberately per-PID rather than writing cgroup.kill, because the reported failure was EINVAL on the cgroup-wide kill — per-PID os.kill is the delivery path that still works.
  • hermes_cli/gateway.py: add ExecStopPost=-{python} -m gateway.cgroup_cleanup to both systemd unit templates (user + system). KillMode=mixed is preserved so the gateway still reaps its own tool-call children during drain (Gateway process not cleaned up on unclean shutdown after provider timeout — bash/sleep children SIGKILL'd by systemd #8202); the reaper is a safety net, not a replacement.
  • scripts/release.py: AUTHOR_MAP entry for the salvaged author.
  • Tests: tests/gateway/test_cgroup_cleanup.py, tests/hermes_cli/test_gateway_service.py.

Validation

Before After
Orphan left in cgroup on stop blocks restart 6+ min reaped by ExecStopPost, restart immediate
Gateway's own children reaped during drain (#8202) unchanged — KillMode=mixed preserved
Targeted tests 181 passing
Live E2E 3 real orphan processes spawned → reaper SIGKILLed all 3, skipped own PID; cgroup v2 path parsed from real /proc/self/cgroup

Salvaged from #37550 by @PRATHAMESH75 (cherry-picked, authorship preserved). Closes #37454.

Infographic

infographic

PRATHAMESH75 and others added 2 commits June 28, 2026 01:35
Long-lived helpers spawned indirectly by tool calls (adb, platform
bridges) were left in the service cgroup after the gateway's main
process exited. When the kernel rejected the deferred cgroup-wide kill
with EINVAL, systemd blocked Restart=always for 6+ minutes, taking
down all platforms and cron windows (#37454).

Add a small ExecStopPost helper (gateway.cgroup_cleanup) that walks
cgroup.procs and sends per-PID SIGKILLs — a different kernel code path
than cgroup.kill, so it succeeds where the cgroup-wide write failed.
KillMode=mixed is preserved so the gateway still reaps its own
tool-call children before systemd intervenes (#8202).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-2b22bdad vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11616 on HEAD, 11615 on base (🆕 +1)

🆕 New issues (1):

Rule Count
unresolved-import 1
First entries
tests/gateway/test_cgroup_cleanup.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues: none

Unchanged: 6097 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

- read_text(encoding='utf-8') (PLW1514)
- # windows-footgun: ok on signal.SIGKILL — module is Linux-only (reads
  /proc, /sys/fs/cgroup; runs from a systemd unit)
- test lambda accepts the new encoding kwarg
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround labels Jun 28, 2026
@teknium1
teknium1 merged commit c23f394 into main Jun 28, 2026
30 checks passed
@teknium1
teknium1 deleted the hermes/hermes-2b22bdad branch June 28, 2026 09:05
@tonydwb

tonydwb commented Jun 28, 2026

Copy link
Copy Markdown

Code Review Summary

Verdict: LGTM

Adds ExecStopPost cgroup reaper to systemd unit to SIGKILL orphaned processes after gateway shutdown. Fixes #37454 (long-lived helpers blocking Restart=always).

  • New gateway/cgroup_cleanup.py module: reads cgroup.procs, SIGKILLs PIDs
  • ExecStopPost=- prefix ensures unit not marked failed on reaper failure
  • KillMode=mixed preserved so gateway reaps own children first
  • 5 unit tests + 2 gateway service tests
  • Per-PID SIGKILL avoids EINVAL on some kernels vs cgroup.kill

Reviewed by Hermes Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gateway: orphan processes in cgroup block systemd restart for 6+ minutes

4 participants