fix(gateway): say a downgrade stop relaunches; pin the exit code end to end

On top of the cherry-picked exit-75 change: the module docstring and the
logged reason still said the stop never asks for a restart, which now
contradicts the exit code. Both now say the service manager starts the
older release's own gateway. A new test drives the bootstrap's exit
verdict and checks the process exits 75, so launchd's
KeepAlive.SuccessfulExit=false relaunches it.

Refs #134889 (GW-14)
Refs #140418
Refs #140719
This commit is contained in:
Teknium committed 2026-10-18 03:24:12 -07:00
1 parent a160f87784
commit 6d82f6a59d
2 files changed
+18 -4

No files matched your search

+7 -4
View File
@@ -7,8 +7,11 @@ profile. Nothing on the older side knows how to stop it. So the gateway watches
revision ``gateway.code_skew`` records. Once the checkout has provably moved (two consecutive
reads, never while an update holds the tree) to code whose state-store modules lack the runtime
ledger, it logs why and stops with the normal drain (a hard exit if that stop fails on the older
tree). It never asks for a restart, so a service manager that relaunches it starts the older
release's own gateway. Every uncertain read keeps it up.
tree). The stop is a plain stop, never a restart of this process, but it exits with the
service-restart code (75): systemd relaunches on any exit, while the launchd plist's
``KeepAlive.SuccessfulExit=false`` parks exit 0 and would leave the gateway down for good. Either
supervisor then starts the older release's own gateway from the tree on disk. Every uncertain read
keeps it up.
"""
from __future__ import annotations
@@ -43,8 +46,8 @@ def downgrade_reason(skew, *, update_in_progress: bool, predates: Callable[[], b
if not skew or update_in_progress or not predates():
return None
return ("This gateway loaded {}, but its checkout is now at {}, a release that predates the gateway "
"runtime. Downgrading is unsupported; stopping (no restart) so the older release can run its "
"own gateway.".format(*tuple(skew)))
"runtime. Downgrading is unsupported; stopping so the service manager starts the older "
"release's own gateway.".format(*tuple(skew)))
async def _stop_or_exit(runner, *, hard_exit: Callable[[int], None] = os._exit) -> None:
@@ -80,3 +80,14 @@ def test_the_tree_probe_reads_the_checkout_on_disk(tmp_path):
git("checkout", "-q", "--detach", "old")
assert rde.tree_predates_runtime(tmp_path) is True
assert rde.tree_predates_runtime(tmp_path / "missing") is False # nothing readable proves nothing
def test_the_downgrade_exit_code_reaches_the_process_exit():
"""The bootstrap's exit verdict raises the runner's code, so launchd (SuccessfulExit=false) sees 75."""
import pytest
from gateway.run_shutdown import _resolve_gateway_exit_verdict
runner = _watch([_SKEW, _SKEW])
runner.should_exit_with_failure, runner._restart_requested, runner._restart_via_service = False, False, False
with pytest.raises(SystemExit) as exc:
_resolve_gateway_exit_verdict(runner, signal_initiated_shutdown=False)
assert exc.value.code == GATEWAY_SERVICE_RESTART_EXIT_CODE