Skip to content

Bound hub shutdown drain with a timeout and error reporting - #446

Merged
TheGreatAxios merged 3 commits into
mainfrom
cl-7133-hub-shutdown-awaits-eight-closes-with-no-timeout-or-catch-a
Aug 29, 2026
Merged

TheGreatAxios merged 3 commits into
mainfrom
cl-7133-hub-shutdown-awaits-eight-closes-with-no-timeout-or-catch-a

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Fixes CL-7133 — https://linear.app/abklabs/issue/CL-7133

Problem

apps/hub/src/index.ts (~3444-3452) had shutdown = async () => { await server.stop(); await hub.close(); process.exit(0); } invoked as void shutdown() from SIGINT/SIGTERM. hub.close() awaits eight sequential closes with no bound — a hung close blocks process exit until SIGKILL, and a rejection is an unhandled rejection rather than a reported error.

Change

  • apps/hub/src/shutdown.ts: new hub-local module with drainWithTimeout (a local copy of apps/sidecar/src/shutdown.ts's primitive — not imported, since the two apps diverge on timeout exit semantics: the sidecar treats a timeout as a clean-enough exit, the hub treats it as a fault) and shutdownHub, which drains within an injectable bound, exits 0 on success, and on a throw or timeout reports the cause through reportError (@corbits/error-sink) and exits non-zero.
  • apps/hub/src/index.ts: wires shutdownHub around hub.close() with a 10s bound, replacing the unbounded await hub.close().
  • Not extracted into a shared package: it's a ~15-line primitive and the two apps' exit semantics already differ, so a shared abstraction would need a parameter immediately.

Tests

apps/hub/src/shutdown.test.ts (red/green): drainWithTimeout's drained/timed-out/failed outcomes, and shutdownHub's exit code + reportError call for each case, using injected exit/report callbacks and millisecond-scale timeouts (no real waits). bun test apps/hub/src/shutdown.test.ts — 6 pass.

Note on the gate

bun run check was OOM-killed mid-run on this shared machine (1-min load average ~30-40 throughout), unrelated to this change. Ran the focused gates instead: apps/hub package tsc --noEmit clean, bun test apps/hub/src/shutdown.test.ts passing, prettier --write + eslint clean on all changed files. CI is the gate for the full suite.

@TheGreatAxios
TheGreatAxios force-pushed the cl-7133-hub-shutdown-awaits-eight-closes-with-no-timeout-or-catch-a branch from 5128a61 to 7dd88c7 Compare August 28, 2026 15:28
Covers drainWithTimeout's drained/timed-out/failed outcomes and
shutdownHub's exit-code and reportError wiring for each, with
injected exit/report callbacks and short timeouts so the suite runs
in milliseconds.
Hub shutdown previously awaited hub.close()'s eight sequential closes
with no timeout: a hung close blocked SIGTERM exit forever, and a
rejection was unhandled. shutdownHub now bounds the drain to 10s and
always exits — 0 on a clean drain, non-zero with the cause reported
through reportError on a throw or timeout — mirroring the sidecar's
drainWithTimeout shape as a local copy rather than a shared import,
since the two apps diverge on timeout exit semantics.

Fixes CL-7133.
Bun's Server.stop waits for open connections and websockets by default,
so leaving it outside the bounded drain kept the hang CL-7133 was filed
against, one line earlier.
@TheGreatAxios
TheGreatAxios force-pushed the cl-7133-hub-shutdown-awaits-eight-closes-with-no-timeout-or-catch-a branch from 7dd88c7 to 5c55ae1 Compare August 29, 2026 04:46
@TheGreatAxios
TheGreatAxios merged commit b763f5a into main Aug 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant