fix(shutdown): kill full child process trees and add synchronous exit handler to force-kill remaining child processes
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-03 - 1.1.1 - fix(shutdown)
|
||||||
|
kill full child process trees and add synchronous exit handler to force-kill remaining child processes
|
||||||
|
|
||||||
|
- Use tree-kill via SmartExit.killTreeByPid to terminate entire process trees (attempt SIGTERM, fallback to SIGKILL).
|
||||||
|
- Replace previous delayed/process.kill calls with awaitable tree-kill for more reliable termination.
|
||||||
|
- Add a synchronous process.on('exit') handler that force-kills any remaining child processes as a last-resort safety net.
|
||||||
|
- No public API changes; internal robustness/bugfix to shutdown behavior.
|
||||||
|
|
||||||
## 2025-12-15 - 1.1.0 - feat(smartexit)
|
## 2025-12-15 - 1.1.0 - feat(smartexit)
|
||||||
Add silent logging option, structured shutdown logs, and killAll return stats
|
Add silent logging option, structured shutdown logs, and killAll return stats
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartexit',
|
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.'
|
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 processesKilled = 0;
|
||||||
let cleanupFunctionsRan = 0;
|
let cleanupFunctionsRan = 0;
|
||||||
|
|
||||||
// Kill child processes
|
// Kill child process trees
|
||||||
if (processes.length > 0) {
|
if (processes.length > 0) {
|
||||||
for (const childProcessArg of processes) {
|
for (const childProcessArg of processes) {
|
||||||
const pid = childProcessArg.pid;
|
const pid = childProcessArg.pid;
|
||||||
if (pid) {
|
if (pid) {
|
||||||
plugins.smartdelay.delayFor(10000).then(() => {
|
try {
|
||||||
if (childProcessArg.killed) {
|
// Use tree-kill to kill the entire process tree, not just the direct child
|
||||||
return;
|
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++;
|
processesKilled++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,14 +134,25 @@ export class SmartExit {
|
|||||||
constructor(optionsArg: ISmartExitOptions = {}) {
|
constructor(optionsArg: ISmartExitOptions = {}) {
|
||||||
this.options = optionsArg;
|
this.options = optionsArg;
|
||||||
|
|
||||||
// do app specific cleaning before exiting
|
// Last-resort synchronous cleanup on exit — 'exit' event cannot await async work.
|
||||||
process.on('exit', async (code) => {
|
// By this point, SIGINT handler should have already called killAll().
|
||||||
if (code === 0) {
|
// This is a safety net for any processes still alive.
|
||||||
const { processesKilled, cleanupFunctionsRan } = await this.killAll();
|
process.on('exit', (code) => {
|
||||||
this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions`);
|
const processes = this.processesToEnd.getArray();
|
||||||
} else {
|
let killed = 0;
|
||||||
const { processesKilled, cleanupFunctionsRan } = await this.killAll();
|
for (const childProcessArg of processes) {
|
||||||
this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions (exit code: ${code})`, true);
|
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