UNPKG

exit-signal

Version:

A Node.js library for handling graceful process exits and shutdown signals.

104 lines (103 loc) 2.97 kB
// src/index.ts import process from "node:process"; import { clearTimeout, setTimeout } from "node:timers"; import { loopWhile } from "@se-oss/deasync"; var isRegistered = false; var isCalled = false; var shouldManuallyExit = true; var exitHooks = new Array(); function addHook(handler, options = {}) { if (!isRegistered) { isRegistered = true; process.once("beforeExit", exit.bind(void 0, 0)); process.once("SIGINT", exit.bind(void 0, 130)); process.once("SIGTERM", exit.bind(void 0, 143)); process.once("exit", () => { shouldManuallyExit = false; }); process.on("message", (message) => { if (message === "shutdown") { exit(130); } }); } const index = exitHooks.push({ handler, options }); return index - 1; } function removeHook(index) { exitHooks[index] = null; } async function exit(exitCode) { if (isCalled) { return; } isCalled = true; let isDone = false; process.exitCode = exitCode; process.channel?.unref(); const handlers = exitHooks.filter((hook) => hook !== null); let forceAfter = Math.max( ...handlers.map(({ options }) => options.timeout).filter((timeout) => typeof timeout === "number") ); if (forceAfter <= 0 || !Number.isFinite(forceAfter)) { forceAfter = Infinity; const hasAsyncHandlers = handlers.some( ({ handler }) => typeof handler === "function" && handler.constructor.name !== "Function" ); if (hasAsyncHandlers) { process.emitWarning( "No timeout was specified for the exit signal handler.\nIf the handler fails to resolve, it can result in a hanging process, potentially leading to a deadlock.\nManual termination of the process may then be necessary.", "Warning", "NES-WARN002" ); } } const promises = []; for (const { handler } of handlers) { if (typeof handler === "function") { if (handler.constructor.name === "Function") { handler(exitCode); } else { promises.push(Promise.resolve(handler(exitCode))); } } } const done = (force = false) => { if (force) { process.emitWarning( "Process forcefully exited.\nThe process was terminated abruptly due to reaching the execution timeout.", "Warning", "NES-WARN001" ); } if (force || shouldManuallyExit) { process.exit(exitCode); } }; await Promise.all(promises).finally(() => { isDone = true; }); if (forceAfter === Infinity) { loopWhile(() => !isDone); return done(); } const asyncTimer = setTimeout(() => done(true), forceAfter); clearTimeout(asyncTimer); const start = Date.now(); loopWhile(() => !isDone && Date.now() - start < forceAfter); return done(); } function onExit(handler, options = {}) { const index = addHook(handler, options); return () => { removeHook(index); }; } function gracefullyExit() { exit(0).then(() => { }); } export { gracefullyExit, onExit };