fix(core): track PIDs independently to survive removeProcess() race during shutdown
The direct child process may die from terminal SIGINT before ProcessLifecycle runs shutdown, causing removeProcess() to clear it. Now killAll() uses a persistent trackedPids Set that is never cleared by removeProcess(), ensuring grandchild process trees are always killed.
This commit is contained in:
@@ -187,22 +187,18 @@ export class ProcessLifecycle {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Synchronous last-resort: SIGKILL any remaining child processes. */
|
/** Synchronous last-resort: SIGKILL any remaining tracked PIDs. */
|
||||||
private handleExit(): void {
|
private handleExit(): void {
|
||||||
const instances = ProcessLifecycle.getInstances();
|
const instances = ProcessLifecycle.getInstances();
|
||||||
let killed = 0;
|
let killed = 0;
|
||||||
|
|
||||||
for (const instance of instances) {
|
for (const instance of instances) {
|
||||||
const processes = instance.processesToEnd.getArray();
|
for (const pid of instance.trackedPids) {
|
||||||
for (const child of processes) {
|
try {
|
||||||
const pid = child.pid;
|
process.kill(pid, 'SIGKILL');
|
||||||
if (pid && !child.killed) {
|
killed++;
|
||||||
try {
|
} catch {
|
||||||
process.kill(pid, 'SIGKILL');
|
// Process already dead
|
||||||
killed++;
|
|
||||||
} catch {
|
|
||||||
// Process may already be dead
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ export class SmartExit {
|
|||||||
// Instance state
|
// Instance state
|
||||||
public processesToEnd = new plugins.lik.ObjectMap<plugins.childProcess.ChildProcess>();
|
public processesToEnd = new plugins.lik.ObjectMap<plugins.childProcess.ChildProcess>();
|
||||||
public cleanupFunctions = new plugins.lik.ObjectMap<() => Promise<any>>();
|
public cleanupFunctions = new plugins.lik.ObjectMap<() => Promise<any>>();
|
||||||
|
/** PIDs tracked independently — survives removeProcess() so shutdown can still kill the tree. */
|
||||||
|
public trackedPids = new Set<number>();
|
||||||
private options: ISmartExitOptions;
|
private options: ISmartExitOptions;
|
||||||
|
|
||||||
private log(message: string, isError = false): void {
|
private log(message: string, isError = false): void {
|
||||||
@@ -93,6 +95,9 @@ export class SmartExit {
|
|||||||
/** Register a child process for cleanup on shutdown. */
|
/** Register a child process for cleanup on shutdown. */
|
||||||
public addProcess(childProcessArg: plugins.childProcess.ChildProcess) {
|
public addProcess(childProcessArg: plugins.childProcess.ChildProcess) {
|
||||||
this.processesToEnd.add(childProcessArg);
|
this.processesToEnd.add(childProcessArg);
|
||||||
|
if (childProcessArg.pid) {
|
||||||
|
this.trackedPids.add(childProcessArg.pid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Register an async cleanup function to run on shutdown. */
|
/** Register an async cleanup function to run on shutdown. */
|
||||||
@@ -126,23 +131,25 @@ export class SmartExit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: Kill child process trees
|
// Phase 2: Kill ALL tracked process trees by PID.
|
||||||
for (const childProcessArg of processes) {
|
// We use trackedPids (not processesToEnd) because the process object may have
|
||||||
const pid = childProcessArg.pid;
|
// been removed by smartshell's exit handler before shutdown runs.
|
||||||
if (pid && !childProcessArg.killed) {
|
// We do NOT check .killed — the direct child may be dead but grandchildren alive.
|
||||||
try {
|
for (const pid of this.trackedPids) {
|
||||||
await SmartExit.killTreeByPid(pid, 'SIGTERM');
|
try {
|
||||||
} catch {
|
await SmartExit.killTreeByPid(pid, 'SIGTERM');
|
||||||
// SIGTERM failed — force kill
|
|
||||||
try {
|
|
||||||
await SmartExit.killTreeByPid(pid, 'SIGKILL');
|
|
||||||
} catch {
|
|
||||||
// Process may already be dead
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processesKilled++;
|
processesKilled++;
|
||||||
|
} catch {
|
||||||
|
// SIGTERM failed — force kill
|
||||||
|
try {
|
||||||
|
await SmartExit.killTreeByPid(pid, 'SIGKILL');
|
||||||
|
processesKilled++;
|
||||||
|
} catch {
|
||||||
|
// Process tree already dead — fine
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.trackedPids.clear();
|
||||||
|
|
||||||
return { processesKilled, cleanupFunctionsRan };
|
return { processesKilled, cleanupFunctionsRan };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user