exit-signal
Version:
A Node.js library for handling graceful process exits and shutdown signals.
104 lines (103 loc) • 2.97 kB
JavaScript
// 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
};