App Shutdown
Graceful shutdown orchestrator — drain in-flight requests, flush audit, close DB, release the cluster lease, then exit. Configurable timeouts and signal handlers wire SIGTERM / SIGINT (and any operator-supplied signals) into a single phase-ordered shutdown.
SIGTERM is the contract between Kubernetes / systemd / docker stop and the framework. Production rolling restarts depend on the server draining cleanly. Without orchestration each subsystem's shutdown races every other subsystem's — the result is dropped requests, half-completed jobs, and stuck cluster leases that block the next pod from acquiring. The orchestrator runs phases in array order with per-phase budgets so a slow phase cannot starve later ones; a phase failure is logged but does not skip the remaining phases (the DB still closes even if jobs drain timed out).
b.appShutdown.standardPhases(components) builds the canonical ordering — mark-draining → scheduler → jobs → websockets → http-server → cluster → db → external-db — given a components map. Operators with custom topology call it directly and prepend or append their own phases. b.appShutdown.pidLock(path) is a single- instance file lock for daemons that must run exactly once on a host; it composes with the orchestrator via addPhase so the lock is released as part of graceful shutdown.
Idempotency: shutdown() is idempotent. Calling it twice returns the same Promise. Signal handlers route through the same call so SIGTERM, SIGINT, an uncaughtException reaching the operator hook, and a manual orchestrator.shutdown() all converge on one orchestration. When b.tracing has an active registry every phase runs inside a span named shutdown. so per-phase durations surface in the operator's tracing exporter.
b.appShutdown.create(opts) #
{
graceMs: number, // total budget across all phases (default 30000)
forceExitMarginMs: number, // headroom after graceMs before the signal-handler watchdog forces exit (default 5000); set the container stop grace above graceMs + this
phases: array, // [{ name, run: async fn, timeoutMs? }]
installSignalHandlers: boolean, // wire SIGTERM/SIGINT (default false)
signals: array, // signal names (default ["SIGTERM","SIGINT"])
exitAfterPhases: boolean, // when true, a non-signal shutdown() also process.exit()s once phases complete (default false — only the signal path exits)
onUncaught: function, // hook for uncaughtException / unhandledRejection
installUncaught: boolean, // wire uncaughtException handler unconditionally
}
Build a graceful-shutdown orchestrator. Returns an instance with shutdown() (idempotent — second call returns the same Promise), middleware() (refuses new requests with 503 + tracks in-flight count), waitInFlight(), addPhase(), installSignals(), uninstallSignals(), draining(), and inFlight(). Each phase has a per-phase budget; the default is remaining grace divided by remaining phases so a slow phase doesn't starve later ones. A phase failure is logged but does not skip the remaining phases.
var orchestrator = b.appShutdown.create({
graceMs: 30000,
phases: [
{ name: "before-stop", run: async function () { return "ok"; } },
{ name: "db", run: function () { return; }, timeoutMs: 5000 },
],
});
var result = await orchestrator.shutdown();
result.ok; // → true
result.phases.length; // → 2
b.appShutdown.standardPhases(components) #
Build the canonical phases array for a components map. The order is mark-draining → scheduler → jobs (or queue) → websockets → http-server → cluster → db → external-db. Each entry carries a conservative timeoutMs. Operators wire the result into b.appShutdown.create({ phases }); with a non-standard topology they prepend or append their own entries to the returned array.
var phases = b.appShutdown.standardPhases({
db: { close: function () { return; } },
});
phases.length; // → 1
phases[0].name; // → "db"
b.appShutdown.pidLock(lockPath) #
Single-instance file lock for daemons that must run exactly once on a host. Returns { acquire, release, held, path }. acquire() writes the current PID atomically (open with O_EXCL + write + fsync) and refuses to acquire if another live process already holds the lock; stale lock files (PID gone) are reaped automatically. On Windows the underlying advisory flock is unavailable, so the lock file's exclusive presence is the lock. Compose with the orchestrator by passing release as a phase via addPhase.
var lock = b.appShutdown.pidLock("/tmp/blamejs-doc-example.pid");
try {
lock.acquire();
lock.held(); // → true
} finally {
lock.release();
}
Last updated 2026-08-08T16:39:49.652Z by seeder.