fix(shutdown): kill full child process trees and add synchronous exit handler to force-kill remaining child processes
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartexit',
|
||||
version: '1.1.0',
|
||||
version: '1.1.1',
|
||||
description: 'A library for managing graceful shutdowns of Node.js processes by handling cleanup operations, including terminating child processes.'
|
||||
}
|
||||
|
||||
45
ts/index.ts
45
ts/index.ts
@@ -99,18 +99,22 @@ export class SmartExit {
|
||||
let processesKilled = 0;
|
||||
let cleanupFunctionsRan = 0;
|
||||
|
||||
// Kill child processes
|
||||
// Kill child process trees
|
||||
if (processes.length > 0) {
|
||||
for (const childProcessArg of processes) {
|
||||
const pid = childProcessArg.pid;
|
||||
if (pid) {
|
||||
plugins.smartdelay.delayFor(10000).then(() => {
|
||||
if (childProcessArg.killed) {
|
||||
return;
|
||||
try {
|
||||
// Use tree-kill to kill the entire process tree, not just the direct child
|
||||
await SmartExit.killTreeByPid(pid, 'SIGTERM');
|
||||
} catch (err) {
|
||||
// SIGTERM failed — force kill the tree
|
||||
try {
|
||||
await SmartExit.killTreeByPid(pid, 'SIGKILL');
|
||||
} catch {
|
||||
// Process may already be dead, ignore
|
||||
}
|
||||
process.kill(pid, 'SIGKILL');
|
||||
});
|
||||
process.kill(pid, 'SIGINT');
|
||||
}
|
||||
processesKilled++;
|
||||
}
|
||||
}
|
||||
@@ -130,14 +134,25 @@ export class SmartExit {
|
||||
constructor(optionsArg: ISmartExitOptions = {}) {
|
||||
this.options = optionsArg;
|
||||
|
||||
// do app specific cleaning before exiting
|
||||
process.on('exit', async (code) => {
|
||||
if (code === 0) {
|
||||
const { processesKilled, cleanupFunctionsRan } = await this.killAll();
|
||||
this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions`);
|
||||
} else {
|
||||
const { processesKilled, cleanupFunctionsRan } = await this.killAll();
|
||||
this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions (exit code: ${code})`, true);
|
||||
// Last-resort synchronous cleanup on exit — 'exit' event cannot await async work.
|
||||
// By this point, SIGINT handler should have already called killAll().
|
||||
// This is a safety net for any processes still alive.
|
||||
process.on('exit', (code) => {
|
||||
const processes = this.processesToEnd.getArray();
|
||||
let killed = 0;
|
||||
for (const childProcessArg of processes) {
|
||||
const pid = childProcessArg.pid;
|
||||
if (pid && !childProcessArg.killed) {
|
||||
try {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
killed++;
|
||||
} catch {
|
||||
// Process may already be dead, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
if (killed > 0) {
|
||||
this.log(`Exit handler: force-killed ${killed} remaining child processes (exit code: ${code})`, code !== 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user