diff options
| author | Adam Malczewski <[email protected]> | 2026-06-05 15:52:42 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-05 15:52:42 +0900 |
| commit | dded4cc5570f47167edf3ea36f232ad4a32bcbc4 (patch) | |
| tree | dbcb57a7daad05b6daa6e24d0e192c47e036890a /packages/host-bin/src/main.ts | |
| parent | 2bf8f9ec9714cbd33a0ecfb7b40dd7a335180fb7 (diff) | |
| download | dispatch-dded4cc5570f47167edf3ea36f232ad4a32bcbc4.tar.gz dispatch-dded4cc5570f47167edf3ea36f232ad4a32bcbc4.zip | |
feat(observability): host-bin supervises the collector (spawn-first / drain-last / restart) — 288 tests
host-bin spawns the out-of-process collector before serving (real Bun.spawn adapted to a ChildHandle), restarts on unexpected exit (backoff + restart-guard cap), drains on SIGINT/SIGTERM (collector final-drain, SIGKILL fallback on timeout). createCollectorSupervisor takes an injected spawn so the lifecycle is unit-tested with a fake (no real subprocess). Collector failures never crash the app (D3 subordinate/fail-safe). New env DISPATCH_TRACE_DB (default ./.dispatch-data/traces.db).
Verified: tsc -b clean, 288 tests (279 + 9 supervisor), biome 0/0. Live (clean single run): 1 collector during, trace DB auto-populated (nested easy-view), 0 collectors after shutdown.
Diffstat (limited to 'packages/host-bin/src/main.ts')
| -rw-r--r-- | packages/host-bin/src/main.ts | 28 |
1 files changed, 28 insertions, 0 deletions
diff --git a/packages/host-bin/src/main.ts b/packages/host-bin/src/main.ts index 0c795b8..4e4b3e4 100644 --- a/packages/host-bin/src/main.ts +++ b/packages/host-bin/src/main.ts @@ -22,6 +22,8 @@ import { extension as sessionOrchestratorExt } from "@dispatch/session-orchestra import { createSqliteStorage, extension as storageSqliteExt } from "@dispatch/storage-sqlite"; import { extension as toolReadFileExt } from "@dispatch/tool-read-file"; import { createServer, extension as transportHttpExt } from "@dispatch/transport-http"; +import type { ChildHandle } from "./collector-supervisor.js"; +import { createCollectorSupervisor } from "./collector-supervisor.js"; import { configMapToAccess, envToConfigMap } from "./config.js"; function createEmptySecrets(): SecretsAccess { @@ -63,6 +65,23 @@ async function boot(): Promise<void> { const logDeps: LogDeps = { now: () => Date.now(), newId: () => crypto.randomUUID() }; const logger = createLogger({ extensionId: "host-bin" }, logSink, logDeps); + const traceDbPath = process.env.DISPATCH_TRACE_DB ?? "./.dispatch-data/traces.db"; + + const supervisor = createCollectorSupervisor({ + spawn: (cmd: string[]) => { + const proc = Bun.spawn(cmd, { stdout: "inherit", stderr: "inherit" }); + const handle: ChildHandle = { + kill: (signal?: string) => proc.kill(signal as NodeJS.Signals), + exited: proc.exited, + }; + return handle; + }, + journalPath, + dbPath: traceDbPath, + logger: logger.child({ extensionId: "collector-supervisor" }), + }); + supervisor.start(); + const dbPath = process.env.DISPATCH_DB ?? "./.dispatch-data/dispatch.db"; mkdirSync(dirname(dbPath), { recursive: true }); const sqliteBackend = createSqliteStorage({ path: dbPath }); @@ -100,6 +119,15 @@ async function boot(): Promise<void> { // Port precedence: BACKEND_PORT (the rewrite's assigned port) → PORT → default. const port = Number(process.env.BACKEND_PORT) || Number(process.env.PORT) || 24203; const server = Bun.serve({ fetch: app.fetch, port }); + + const shutdown = async () => { + logger.info("Shutting down — draining collector"); + await supervisor.stop(); + process.exit(0); + }; + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + logger.info(`Dispatch listening on http://localhost:${server.port}`); console.info(`Dispatch listening on http://localhost:${server.port}`); } |
