Harden exec completion after child exit

This commit is contained in:
Tak Hoffman
2026-03-27 17:03:49 -05:00
parent b6e8e43611
commit 81e247c63d

View File

@@ -246,6 +246,8 @@ export async function runCommandWithTimeout(
let settled = false;
let timedOut = false;
let noOutputTimedOut = false;
let childExitState: { code: number | null; signal: NodeJS.Signals | null } | null = null;
let closeFallbackTimer: NodeJS.Timeout | null = null;
let noOutputTimer: NodeJS.Timeout | null = null;
const shouldTrackOutputTimeout =
typeof noOutputTimeoutMs === "number" &&
@@ -260,6 +262,14 @@ export async function runCommandWithTimeout(
noOutputTimer = null;
};
const clearCloseFallbackTimer = () => {
if (!closeFallbackTimer) {
return;
}
clearTimeout(closeFallbackTimer);
closeFallbackTimer = null;
};
const armNoOutputTimer = () => {
if (!shouldTrackOutputTimeout || settled) {
return;
@@ -304,8 +314,22 @@ export async function runCommandWithTimeout(
settled = true;
clearTimeout(timer);
clearNoOutputTimer();
clearCloseFallbackTimer();
reject(err);
});
child.on("exit", (code, signal) => {
childExitState = { code, signal };
if (settled || closeFallbackTimer) {
return;
}
closeFallbackTimer = setTimeout(() => {
if (settled) {
return;
}
child.stdout?.destroy();
child.stderr?.destroy();
}, 250);
});
child.on("close", (code, signal) => {
if (settled) {
return;
@@ -313,25 +337,28 @@ export async function runCommandWithTimeout(
settled = true;
clearTimeout(timer);
clearNoOutputTimer();
clearCloseFallbackTimer();
const resolvedCode = childExitState?.code ?? code;
const resolvedSignal = childExitState?.signal ?? signal;
const termination = noOutputTimedOut
? "no-output-timeout"
: timedOut
? "timeout"
: signal != null
: resolvedSignal != null
? "signal"
: "exit";
const normalizedCode =
termination === "timeout" || termination === "no-output-timeout"
? code === 0
? resolvedCode === 0
? 124
: code
: code;
: resolvedCode
: resolvedCode;
resolve({
pid: child.pid ?? undefined,
stdout,
stderr,
code: normalizedCode,
signal,
signal: resolvedSignal,
killed: child.killed,
termination,
noOutputTimedOut,