From 5ded28896d52c1f1e3fcd064a128e432509236be Mon Sep 17 00:00:00 2001 From: "didericis (codex)" Date: Thu, 23 Jul 2026 18:55:27 -0400 Subject: [PATCH] fix(gateway): restart dead children before completion check --- bot_bottle/gateway_init.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/bot_bottle/gateway_init.py b/bot_bottle/gateway_init.py index 9b6ed6c..0f24b26 100644 --- a/bot_bottle/gateway_init.py +++ b/bot_bottle/gateway_init.py @@ -9,8 +9,7 @@ Failure policy: when a child dies unexpectedly, the supervisor restarts it automatically and logs the restart. The gateway stays up; a temporary loss of one daemon (e.g. egress OOM-killed) is recovered without manual container recreation. The supervisor -itself exits only when (a) the operator sends SIGTERM/SIGINT, or -(b) every child has died. +itself exits only when the operator sends SIGTERM/SIGINT. Daemon subset is env-driven via `BOT_BOTTLE_GATEWAY_DAEMONS=egress` for callers that don't use git-gate or supervise. Default: all @@ -221,9 +220,10 @@ class _Supervisor: """One iteration of the watch loop. Returns True when every child has exited and the supervisor can return. - A child dying unexpectedly is logged but does NOT initiate - shutdown — see the module docstring's failure-policy - section. Shutdown is signal-driven only.""" + A child dying unexpectedly is logged and restarted but does + NOT initiate shutdown — see the module docstring's + failure-policy section. Shutdown is signal-driven only.""" + restarted_children = bool(self._restart_requested) self._drain_restart_requests() for spec, p in self.procs: @@ -237,6 +237,13 @@ class _Supervisor: else: _log(f"{spec.name} exited with code {rc}") + # Restart deaths discovered above before checking whether all + # processes are done. Deferring this until the next tick would make a + # single-daemon supervisor return True and exit with the restart still + # queued. + restarted_children |= bool(self._restart_requested) + self._drain_restart_requests() + if self.shutdown_at is not None: elapsed = time.monotonic() - self.shutdown_at if elapsed > _GRACE_SECONDS: @@ -250,7 +257,10 @@ class _Supervisor: ) self._sigkill_all() - done = all(p.poll() is not None for _, p in self.procs) + done = ( + not restarted_children + and all(p.poll() is not None for _, p in self.procs) + ) if done: for _, p in self.procs: if p.stdout is not None: