Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions hermes_cli/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,11 @@ def _graceful_restart_via_sigusr1(pid: int, drain_timeout: float) -> bool:

SIGUSR1 is wired in gateway/run.py to ``request_restart(via_service=True)``
which drains in-flight agent runs (up to ``agent.restart_drain_timeout``
seconds), then exits with code 75. Both systemd (``Restart=always``
+ ``RestartForceExitStatus=75``) and launchd (``KeepAlive.SuccessfulExit
= false``) relaunch the process after the graceful exit.
seconds), then exits with code 75. Systemd units generated by Hermes use
``Restart=on-failure`` together with ``RestartForceExitStatus=75`` so the
service is relaunched after the graceful exit without reviving clean
``--replace`` takeovers. launchd still uses ``KeepAlive.SuccessfulExit =
false`` for the same relaunch behavior.

This is the drain-aware alternative to ``systemctl restart`` / ``SIGTERM``,
which SIGKILL in-flight agents after a short timeout.
Expand Down Expand Up @@ -2217,7 +2219,7 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)
Environment="PATH={sane_path}"
Environment="VIRTUAL_ENV={venv_dir}"
Environment="HERMES_HOME={hermes_home}"
Restart=always
Restart=on-failure
RestartSec=5
RestartMaxDelaySec=300
RestartSteps=5
Expand Down Expand Up @@ -2252,7 +2254,7 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)
Environment="PATH={sane_path}"
Environment="VIRTUAL_ENV={venv_dir}"
Environment="HERMES_HOME={hermes_home}"
Restart=always
Restart=on-failure
RestartSec=5
RestartMaxDelaySec=300
RestartSteps=5
Expand Down Expand Up @@ -3236,7 +3238,7 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):
print()

# Exit with code 1 if gateway fails to connect any platform,
# so systemd Restart=always will retry on transient errors
# so systemd Restart=on-failure will retry on transient errors
verbosity = None if quiet else verbose

# ── Exit-path diagnostics ────────────────────────────────────────────
Expand Down
4 changes: 4 additions & 0 deletions tests/hermes_cli/test_gateway_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ def test_user_unit_avoids_recursive_execstop_and_uses_extended_stop_timeout(self
assert "ExecStart=" in unit
assert "ExecStop=" not in unit
assert "ExecReload=/bin/kill -USR1 $MAINPID" in unit
assert "Restart=on-failure" in unit
assert "Restart=always" not in unit
assert f"RestartForceExitStatus={GATEWAY_SERVICE_RESTART_EXIT_CODE}" in unit
# TimeoutStopSec must exceed the default drain_timeout (60s) so
# systemd doesn't SIGKILL the cgroup before post-interrupt cleanup
Expand Down Expand Up @@ -387,6 +389,8 @@ def test_system_unit_avoids_recursive_execstop_and_uses_extended_stop_timeout(se
assert "ExecStart=" in unit
assert "ExecStop=" not in unit
assert "ExecReload=/bin/kill -USR1 $MAINPID" in unit
assert "Restart=on-failure" in unit
assert "Restart=always" not in unit
assert f"RestartForceExitStatus={GATEWAY_SERVICE_RESTART_EXIT_CODE}" in unit
# TimeoutStopSec must exceed the default drain_timeout (60s) so
# systemd doesn't SIGKILL the cgroup before post-interrupt cleanup
Expand Down
Loading