import { setTimeout as sleep } from 'node:timers/promises' /** After a signal, give cleanup this long before exiting anyway. */ const FORCE_EXIT_MS = 25_000 /** * Graceful shutdown for the long-running commands. SIGTERM (docker stop) and * SIGINT wake up `wait()` and `sleep()`, so workers can finish their active * jobs and close their connections before the process exits. */ class Shutdown { private readonly controller = new AbortController() constructor() { for (const signal of ['SIGTERM', 'SIGINT'] as const) { process.once(signal, () => { this.controller.abort() setTimeout(() => process.exit(0), FORCE_EXIT_MS).unref() }) } } get requested(): boolean { return this.controller.signal.aborted } /** Resolves when a shutdown signal arrives. */ wait(): Promise { if (this.requested) return Promise.resolve() return new Promise((resolve) => this.controller.signal.addEventListener('abort', () => resolve(), { once: true }) ) } /** Sleeps, but returns early on shutdown. */ async sleep(ms: number): Promise { try { await sleep(ms, undefined, { signal: this.controller.signal }) } catch { // aborted by a shutdown signal } } } export default new Shutdown()