87 lines
2.0 KiB
TypeScript
87 lines
2.0 KiB
TypeScript
import type { ChildProcess } from "node:child_process";
|
|
|
|
interface PostExitStdioGuardOptions {
|
|
idleMs: number;
|
|
hardMs: number;
|
|
}
|
|
|
|
export interface ChildWithPipedStdio {
|
|
stdout: ChildProcess["stdout"];
|
|
stderr: ChildProcess["stderr"];
|
|
on: ChildProcess["on"];
|
|
}
|
|
|
|
export interface ChildWithKill {
|
|
kill(signal?: NodeJS.Signals | number): boolean;
|
|
}
|
|
|
|
export function trySignalChild(child: ChildWithKill, signal: NodeJS.Signals): boolean {
|
|
try {
|
|
return child.kill(signal);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function attachPostExitStdioGuard(child: ChildWithPipedStdio, options: PostExitStdioGuardOptions): () => void {
|
|
const { idleMs, hardMs } = options;
|
|
let exited = false;
|
|
let stdoutEnded = false;
|
|
let stderrEnded = false;
|
|
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
let hardTimer: ReturnType<typeof setTimeout> | undefined;
|
|
|
|
const destroyUnendedStdio = (): void => {
|
|
if (!stdoutEnded) {
|
|
try {
|
|
child.stdout?.destroy();
|
|
} catch {}
|
|
}
|
|
if (!stderrEnded) {
|
|
try {
|
|
child.stderr?.destroy();
|
|
} catch {}
|
|
}
|
|
};
|
|
|
|
const clearTimers = (): void => {
|
|
if (idleTimer) {
|
|
clearTimeout(idleTimer);
|
|
idleTimer = undefined;
|
|
}
|
|
if (hardTimer) {
|
|
clearTimeout(hardTimer);
|
|
hardTimer = undefined;
|
|
}
|
|
};
|
|
|
|
const armIdleTimer = () => {
|
|
if (!exited) return;
|
|
if (idleTimer) clearTimeout(idleTimer);
|
|
idleTimer = setTimeout(destroyUnendedStdio, idleMs);
|
|
idleTimer.unref();
|
|
};
|
|
|
|
child.stdout?.on("data", armIdleTimer);
|
|
child.stderr?.on("data", armIdleTimer);
|
|
child.stdout?.on("end", () => {
|
|
stdoutEnded = true;
|
|
if (stdoutEnded && stderrEnded) clearTimers();
|
|
});
|
|
child.stderr?.on("end", () => {
|
|
stderrEnded = true;
|
|
if (stdoutEnded && stderrEnded) clearTimers();
|
|
});
|
|
child.on("exit", () => {
|
|
exited = true;
|
|
armIdleTimer();
|
|
if (hardTimer) return;
|
|
hardTimer = setTimeout(destroyUnendedStdio, hardMs);
|
|
hardTimer.unref();
|
|
});
|
|
child.on("close", clearTimers);
|
|
child.on("error", clearTimers);
|
|
|
|
return clearTimers;
|
|
}
|