fix(s6): register profile gateways without auto-starting (#46266)
* fix(s6): prevent profile create from auto-starting gateway service When hermes profile create runs inside an s6 container, _maybe_register_gateway_service() calls register_profile_gateway() which creates the service directory and triggers s6-svscanctl -a. Previously the service always started immediately, causing profiles that share the main gateway's bot token (e.g. Kanban worker profiles) to fail with a token-lock conflict and persist gateway_state: running — becoming zombies that resurrect on every container restart. Wire the existing start_now parameter through the S6 implementation: when start_now=False, write a marker file (same pattern as container_boot.py _register_gateway_slot) so s6-supervise leaves the service stopped until the user explicitly runs hermes -p <profile> gateway start. 4 files, +61/-6, 4 new tests (all passing). * test(docker): wait for gateway running state before restart --------- Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
This commit is contained in:
@@ -1268,7 +1268,7 @@ def _maybe_register_gateway_service(profile_name: str) -> None:
|
||||
if not mgr.supports_runtime_registration():
|
||||
return # host backend; no-op
|
||||
try:
|
||||
mgr.register_profile_gateway(profile_name)
|
||||
mgr.register_profile_gateway(profile_name, start_now=False)
|
||||
except ValueError:
|
||||
# Already registered (e.g. the container-boot reconciler ran
|
||||
# first and brought up a stale slot). That's fine.
|
||||
|
||||
@@ -77,6 +77,7 @@ class ServiceManager(Protocol):
|
||||
profile: str,
|
||||
*,
|
||||
extra_env: dict[str, str] | None = None,
|
||||
start_now: bool = True,
|
||||
) -> None: ...
|
||||
def unregister_profile_gateway(self, profile: str) -> None: ...
|
||||
def list_profile_gateways(self) -> list[str]: ...
|
||||
@@ -182,6 +183,7 @@ class _RegistrationUnsupportedMixin:
|
||||
profile: str,
|
||||
*,
|
||||
extra_env: dict[str, str] | None = None,
|
||||
start_now: bool = True,
|
||||
) -> None:
|
||||
raise NotImplementedError(
|
||||
f"{type(self).__name__} does not support runtime profile "
|
||||
@@ -830,15 +832,15 @@ class S6ServiceManager:
|
||||
profile: str,
|
||||
*,
|
||||
extra_env: dict[str, str] | None = None,
|
||||
start_now: bool = True,
|
||||
) -> None:
|
||||
"""Create the s6 service directory for a profile gateway.
|
||||
|
||||
Triggers ``s6-svscanctl -a`` so s6-svscan picks the new directory
|
||||
up immediately. The service is created in the *up* state — to
|
||||
register without auto-starting, follow up with ``stop(profile)``
|
||||
(or pass the start flag via the future ``start_now=False`` arg,
|
||||
which the Phase 4 reconciliation path uses via a ``down``
|
||||
marker file written directly).
|
||||
up immediately. When *start_now* is ``True`` (the default) the
|
||||
service starts immediately; when ``False`` a ``down`` marker file
|
||||
is written so s6-supervise leaves the service stopped until the
|
||||
user explicitly runs ``hermes -p <profile> gateway start``.
|
||||
|
||||
Raises:
|
||||
ValueError: if the profile name is invalid or the service
|
||||
@@ -886,6 +888,13 @@ class S6ServiceManager:
|
||||
# rationale.
|
||||
_seed_supervise_skeleton(tmp_dir)
|
||||
|
||||
# When start_now is False, write a `down` marker so
|
||||
# s6-supervise does not auto-start the service on rescan.
|
||||
# Mirrors the same pattern in container_boot.py
|
||||
# _register_gateway_slot when start=False.
|
||||
if not start_now:
|
||||
(tmp_dir / "down").touch()
|
||||
|
||||
tmp_dir.rename(svc_dir)
|
||||
except Exception:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
Reference in New Issue
Block a user