





















@@ -20,6 +20,7 @@ const DEFAULTS = {
2020readyTimeoutMs: 20_000,
2121readySettleMs: 500,
2222sigkillGraceMs: 10_000,
23+sigkillExitGraceMs: 2_000,
2324cpuWarnMs: 1_000,
2425cpuFailMs: 8_000,
2526distRuntimeFileGrowthMax: 200,
@@ -69,6 +70,9 @@ function parseArgs(argv) {
6970case "--sigkill-grace-ms":
7071options.sigkillGraceMs = Number(readValue());
7172break;
73+case "--sigkill-exit-grace-ms":
74+options.sigkillExitGraceMs = Number(readValue());
75+break;
7276case "--cpu-warn-ms":
7377options.cpuWarnMs = Number(readValue());
7478break;
@@ -467,10 +471,6 @@ async function runTimedWatch(options, outputDir) {
467471stderr += String(chunk);
468472});
469473470-const exitPromise = new Promise((resolve) => {
471-child.on("exit", (code, signal) => resolve({ code, signal }));
472-});
473-474474let watchPid = null;
475475for (let attempt = 0; attempt < 50; attempt += 1) {
476476if (fs.existsSync(pidFilePath)) {
@@ -491,30 +491,7 @@ async function runTimedWatch(options, outputDir) {
491491await sleep(options.windowMs);
492492const idleCpuEndMs = watchPid ? readProcessTreeCpuMs(watchPid) : null;
493493494-if (watchPid) {
495-try {
496-process.kill(watchPid, "SIGTERM");
497-} catch {
498-// ignore
499-}
500-}
501-502-const gracefulExit = await Promise.race([
503-exitPromise,
504-sleep(options.sigkillGraceMs).then(() => null),
505-]);
506-507-if (gracefulExit === null) {
508-if (watchPid) {
509-try {
510-process.kill(watchPid, "SIGKILL");
511-} catch {
512-// ignore
513-}
514-}
515-}
516-517-const exit = (await exitPromise) ?? { code: null, signal: null };
494+const exit = await stopTimedWatchChild(child, watchPid, options);
518495fs.writeFileSync(stdoutPath, stdout, "utf8");
519496fs.writeFileSync(stderrPath, stderr, "utf8");
520497const timing = fs.existsSync(timeFilePath)
@@ -535,6 +512,56 @@ async function runTimedWatch(options, outputDir) {
535512};
536513}
537514515+export async function stopTimedWatchChild(child, watchPid, options, deps = {}) {
516+const killProcess = deps.killProcess ?? ((pid, signal) => process.kill(pid, signal));
517+const currentExit = () =>
518+child.exitCode !== null || child.signalCode !== null
519+ ? { code: child.exitCode, signal: child.signalCode }
520+ : null;
521+const exited = new Promise((resolve) => {
522+child.once("exit", (code, signal) => resolve({ code, signal }));
523+});
524+const waitForExit = async (ms) =>
525+currentExit() ?? (await Promise.race([exited, sleep(ms).then(() => null)]));
526+const signalWatchProcess = (signal) => {
527+if (!watchPid) {
528+return;
529+}
530+try {
531+killProcess(watchPid, signal);
532+} catch {
533+// ignore
534+}
535+};
536+537+const existingExit = currentExit();
538+if (existingExit) {
539+return existingExit;
540+}
541+542+signalWatchProcess("SIGTERM");
543+const gracefulExit = await waitForExit(options.sigkillGraceMs);
544+if (gracefulExit) {
545+return gracefulExit;
546+}
547+548+signalWatchProcess("SIGKILL");
549+const killedExit = await waitForExit(options.sigkillExitGraceMs ?? DEFAULTS.sigkillExitGraceMs);
550+if (killedExit) {
551+return killedExit;
552+}
553+554+releaseUnsettledWatchChild(child);
555+return { code: null, signal: "SIGKILL" };
556+}
557+558+function releaseUnsettledWatchChild(child) {
559+child.stdin?.destroy?.();
560+child.stdout?.destroy?.();
561+child.stderr?.destroy?.();
562+child.unref?.();
563+}
564+538565function parsePathFile(filePath) {
539566return fs
540567.readFileSync(filePath, "utf8")
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。