fix(processmanager): Improve process lifecycle handling and cleanup in daemon, monitors and wrappers
This commit is contained in:
@@ -23,6 +23,26 @@ export class ProcessWrapper extends EventEmitter {
|
||||
private runId: string = '';
|
||||
private stdoutRemainder: string = '';
|
||||
private stderrRemainder: string = '';
|
||||
|
||||
// Helper: send a signal to the process and all its children (best-effort)
|
||||
private async killProcessTree(signal: NodeJS.Signals): Promise<void> {
|
||||
if (!this.process || !this.process.pid) return;
|
||||
const rootPid = this.process.pid;
|
||||
await new Promise<void>((resolve) => {
|
||||
plugins.psTree(rootPid, (err: any, children: ReadonlyArray<{ PID: string }>) => {
|
||||
const pids: number[] = [rootPid, ...children.map((c) => Number(c.PID)).filter((n) => Number.isFinite(n))];
|
||||
for (const pid of pids) {
|
||||
try {
|
||||
// Always signal individual PIDs to avoid accidentally targeting unrelated groups
|
||||
process.kill(pid, signal);
|
||||
} catch {
|
||||
// ignore ESRCH/EPERM
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
constructor(options: IProcessWrapperOptions) {
|
||||
super();
|
||||
@@ -193,17 +213,13 @@ export class ProcessWrapper extends EventEmitter {
|
||||
// First try SIGTERM for graceful shutdown
|
||||
if (this.process.pid) {
|
||||
try {
|
||||
this.logger.debug(`Sending SIGTERM to process ${this.process.pid}`);
|
||||
try {
|
||||
// Try to signal the whole process group on POSIX to ensure children get the signal too
|
||||
if (process.platform !== 'win32') {
|
||||
process.kill(-Math.abs(this.process.pid), 'SIGTERM');
|
||||
} else {
|
||||
process.kill(this.process.pid, 'SIGTERM');
|
||||
}
|
||||
} catch {
|
||||
// Fallback to direct process kill if group kill fails
|
||||
process.kill(this.process.pid, 'SIGTERM');
|
||||
this.logger.debug(`Sending SIGTERM to process tree rooted at ${this.process.pid}`);
|
||||
await this.killProcessTree('SIGTERM');
|
||||
|
||||
// If the process already exited, return immediately
|
||||
if (typeof this.process.exitCode === 'number') {
|
||||
this.logger.debug('Process already exited, no need to wait');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for exit or escalate
|
||||
@@ -218,26 +234,15 @@ export class ProcessWrapper extends EventEmitter {
|
||||
const onExit = () => cleanup();
|
||||
this.process!.once('exit', onExit);
|
||||
|
||||
const killTimer = setTimeout(() => {
|
||||
const killTimer = setTimeout(async () => {
|
||||
if (!this.process || !this.process.pid) return cleanup();
|
||||
this.logger.warn(
|
||||
`Process ${this.process.pid} did not exit gracefully, force killing...`,
|
||||
);
|
||||
this.addSystemLog(
|
||||
'Process did not exit gracefully, force killing...',
|
||||
`Process ${this.process.pid} did not exit gracefully, force killing tree...`,
|
||||
);
|
||||
this.addSystemLog('Process did not exit gracefully, force killing...');
|
||||
try {
|
||||
if (process.platform !== 'win32') {
|
||||
process.kill(-Math.abs(this.process.pid), 'SIGKILL');
|
||||
} else {
|
||||
process.kill(this.process.pid, 'SIGKILL');
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.debug(
|
||||
`Failed to send SIGKILL, process probably already exited: ${error?.message || String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
await this.killProcessTree('SIGKILL');
|
||||
} catch {}
|
||||
// Give a short grace period after SIGKILL
|
||||
setTimeout(() => cleanup(), 500);
|
||||
}, 5000);
|
||||
|
Reference in New Issue
Block a user