mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(gateway): restart --all stamps the marker after the old host is gone
Written before the stop, the marker was visible to the still-draining old host: a platform reconnecting in that window replayed "Gateway online" and unlinked it, so the new host booted silent. restart_all_verb_scope now yields the stamp and _restart_all_as_host runs it once the old host has exited and before the start, the same order the Windows restart uses. The Windows restart test runs on Windows (platforms marker) instead of patching out the host check.
This commit is contained in:
1 parent
bc8e9107b6
commit
8e5ea80dfb
4 files changed
+14
-13
No files matched your search
@@ -5402,11 +5402,11 @@ def _restart_all(system: bool, detach: bool = False) -> None:
|
||||
sys.exit(GATEWAY_FATAL_CONFIG_EXIT_CODE)
|
||||
# No live owner: stop/kill/start as the host root, not as the invoking profile (whose own
|
||||
# gateway the run-side guard would refuse — the host stayed down after a Desktop update).
|
||||
with restart_all_verb_scope(owner):
|
||||
_restart_all_as_host(owner, system, detach)
|
||||
with restart_all_verb_scope(owner) as owe_online_notice:
|
||||
_restart_all_as_host(owner, system, detach, owe_online_notice)
|
||||
|
||||
|
||||
def _restart_all_as_host(owner, system: bool, detach: bool = False) -> None:
|
||||
def _restart_all_as_host(owner, system: bool, detach: bool = False, owe_online_notice=lambda: None) -> None:
|
||||
service_stopped = _stop_installed_service(system)
|
||||
if owner is not None:
|
||||
# Stop ONLY the host process: its served set comes back with it, and any per-profile
|
||||
@@ -5426,8 +5426,8 @@ def _restart_all_as_host(owner, system: bool, detach: bool = False) -> None:
|
||||
# Retract the stopped owner's rendezvous record. Leaving it made this very function a silent
|
||||
# no-op: the re-entered `gateway run` below read the corpse's record and ATTACHED to it.
|
||||
_discard_dead_host_record()
|
||||
# The retraction only invalidates when it removed a record; the pre-stop memo must go either way.
|
||||
_forget_host_gateway_probe()
|
||||
_forget_host_gateway_probe() # the retraction invalidates only a removed record; drop the memo anyway
|
||||
owe_online_notice() # old host gone, new one not started: only the new boot can see it
|
||||
|
||||
print("Starting gateway...")
|
||||
# Even without a registered task, gateway_windows.start() uses the detached launcher.
|
||||
|
||||
@@ -51,12 +51,13 @@ def restart_all_verb_scope(owner):
|
||||
|
||||
The verb stops the host with a service call or a SIGTERM, so the old process never runs the
|
||||
in-band restart path that stamps the planned-restart marker; without it the next boot reads a
|
||||
plain stop and every home channel stays silent — #142515's gap, on every OS.
|
||||
plain stop and every home channel stays silent — #142515's gap, on every OS. Yields the stamp
|
||||
for the caller to run once the old host is gone and before the start: a marker written while the
|
||||
old host still drains can be replayed and unlinked by it, and the new host then boots silent.
|
||||
"""
|
||||
from gateway.restart_notice import write_planned_restart_marker
|
||||
with host_scope_for_all_verb(owner):
|
||||
write_planned_restart_marker()
|
||||
yield
|
||||
yield write_planned_restart_marker
|
||||
|
||||
|
||||
def profile_lifecycle(command: str, args) -> bool:
|
||||
|
||||
@@ -22,7 +22,7 @@ def hermes_home(tmp_path, monkeypatch):
|
||||
return tmp_path
|
||||
|
||||
|
||||
def test_restart_all_stamps_the_marker_before_the_host_is_stopped(hermes_home, monkeypatch):
|
||||
def test_restart_all_stamps_the_marker_after_the_host_is_stopped(hermes_home, monkeypatch):
|
||||
from hermes_cli import gateway as gw
|
||||
|
||||
marker = hermes_home / ".restart_pending.json"
|
||||
@@ -37,7 +37,7 @@ def test_restart_all_stamps_the_marker_before_the_host_is_stopped(hermes_home, m
|
||||
monkeypatch.setattr(gw, "_discard_dead_host_record", lambda: None)
|
||||
monkeypatch.setattr(gw, "_forget_host_gateway_probe", lambda: None)
|
||||
monkeypatch.setattr(gw, "_installed_service_kind_for", lambda windows: "systemd")
|
||||
monkeypatch.setattr(gw, "_service_call", lambda kind, verb, system: None)
|
||||
monkeypatch.setattr(gw, "_service_call", lambda kind, verb, system: seen.setdefault("marker_at_start", marker.exists()))
|
||||
monkeypatch.setattr(gw, "is_windows", False)
|
||||
|
||||
owner = SimpleNamespace(pid=4242, profiles=["default"], home=hermes_home, describe=lambda: "host")
|
||||
@@ -47,5 +47,5 @@ def test_restart_all_stamps_the_marker_before_the_host_is_stopped(hermes_home, m
|
||||
# The verb itself, exactly as the CLI dispatches it.
|
||||
gw._restart_all(system=False)
|
||||
|
||||
assert seen["marker_at_stop"] is True, "the host must be stopped with the notice already owed"
|
||||
assert marker.exists(), "the restarted host's boot pass reads this marker"
|
||||
assert seen["marker_at_stop"] is False, "the still-draining old host must not see the marker"
|
||||
assert seen["marker_at_start"] is True, "the restarted host's boot pass reads this marker"
|
||||
@@ -23,13 +23,13 @@ def hermes_home(tmp_path, monkeypatch):
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.mark.platforms("windows")
|
||||
def test_restart_stamps_the_marker_after_the_old_gateway_is_gone(hermes_home, monkeypatch):
|
||||
from hermes_cli import gateway as gw_facade
|
||||
from hermes_cli import gateway_windows as gw
|
||||
|
||||
marker = hermes_home / MARKER
|
||||
seen: dict[str, bool] = {}
|
||||
monkeypatch.setattr(gw, "_assert_windows", lambda: None)
|
||||
monkeypatch.setattr(gw, "_wait_for_gateway_absent", lambda timeout_s=30.0: True)
|
||||
monkeypatch.setattr(gw_facade, "_wait_for_api_server_port_free", lambda: None)
|
||||
monkeypatch.setattr(gw, "stop", lambda: seen.setdefault("marker_at_stop", marker.exists()))
|
||||
|
||||
Reference in new issue
Block a user