

























@@ -22,6 +22,7 @@ type RunCommandOptions = {
2222const DEFAULT_OUTPUT_LIMIT = 128 * 1024;
2323const DEFAULT_FETCH_BODY_LIMIT = 1024 * 1024;
2424const KILL_GRACE_MS = readKillGraceMs();
25+const PROCESS_TREE_EXIT_POLL_MS = 50;
2526const SIGNAL_EXIT_CODES = {
2627SIGHUP: 129,
2728SIGINT: 130,
@@ -141,8 +142,8 @@ export function runCommand(
141142let stderr = "";
142143let settled = false;
143144let killTimer: NodeJS.Timeout | undefined;
144-let pendingTimeoutReject: (() => void) | undefined;
145145let timedOutError: Error | undefined;
146+let forceKillAt: number | undefined;
146147const timeoutMs = Math.max(1, options.timeoutMs);
147148const timeoutKillGraceMs = Math.max(0, options.timeoutKillGraceMs ?? KILL_GRACE_MS);
148149const clearTimers = () => {
@@ -168,10 +169,11 @@ export function runCommand(
168169`${command} ${args.join(" ")} timed out after ${timeoutMs}ms\n${stdout}${stderr}`,
169170);
170171activeChildTree.killChildTree("SIGTERM");
172+forceKillAt = Date.now() + timeoutKillGraceMs;
171173killTimer = setTimeout(() => {
172174killTimer = undefined;
175+forceKillAt = undefined;
173176activeChildTree.killChildTree("SIGKILL");
174-pendingTimeoutReject?.();
175177}, timeoutKillGraceMs);
176178}, timeoutMs);
177179timeout.unref?.();
@@ -195,15 +197,11 @@ export function runCommand(
195197}
196198if (timedOutError && killTimer && childProcessTreeMayStillExist(child)) {
197199const error = timedOutError;
198-pendingTimeoutReject = () => {
199-if (settled) {
200-return;
201-}
202-settled = true;
203-clearTimers();
204-activeChildTree.unregister();
205-reject(error);
206-};
200+void finishTimedOutChildProcessTree(child, activeChildTree, {
201+ forceKillAt,
202+ killTimer,
203+ timeoutKillGraceMs,
204+}).then(() => fail(error), fail);
207205return;
208206}
209207settled = true;
@@ -223,6 +221,29 @@ export function runCommand(
223221});
224222}
225223224+async function finishTimedOutChildProcessTree(
225+child: ReturnType<typeof spawn>,
226+activeChildTree: ReturnType<typeof registerActiveChildProcessTree>,
227+options: {
228+forceKillAt: number | undefined;
229+killTimer: NodeJS.Timeout;
230+timeoutKillGraceMs: number;
231+},
232+) {
233+const graceRemainingMs =
234+options.forceKillAt === undefined
235+ ? options.timeoutKillGraceMs
236+ : Math.max(0, options.forceKillAt - Date.now());
237+if (graceRemainingMs > 0) {
238+await waitForChildProcessTreeExit(child, graceRemainingMs);
239+}
240+clearTimeout(options.killTimer);
241+if (childProcessTreeMayStillExist(child)) {
242+activeChildTree.killChildTree("SIGKILL");
243+await waitForChildProcessTreeExit(child, options.timeoutKillGraceMs);
244+}
245+}
246+226247function signalChildProcessTree(child: ReturnType<typeof spawn>, signal: NodeJS.Signals) {
227248if (process.platform !== "win32" && child.pid) {
228249try {
@@ -247,6 +268,19 @@ function childProcessTreeMayStillExist(child: ReturnType<typeof spawn>) {
247268}
248269}
249270271+async function waitForChildProcessTreeExit(child: ReturnType<typeof spawn>, timeoutMs: number) {
272+const deadlineAt = Date.now() + timeoutMs;
273+while (Date.now() < deadlineAt) {
274+if (!childProcessTreeMayStillExist(child)) {
275+return true;
276+}
277+await new Promise((resolvePoll) => {
278+setTimeout(resolvePoll, PROCESS_TREE_EXIT_POLL_MS);
279+});
280+}
281+return !childProcessTreeMayStillExist(child);
282+}
283+250284function registerActiveChildProcessTree(child: ReturnType<typeof spawn>) {
251285const killChildTree = (signal: NodeJS.Signals) => signalChildProcessTree(child, signal);
252286ACTIVE_CHILD_TREE_KILLERS.add(killChildTree);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。