






















@@ -549,6 +549,159 @@ describe("bundled plugin install/uninstall probe", () => {
549549).not.toContainEqual({ args: "yarn install", pid: 104, ppid: 1 });
550550});
551551552+it.runIf(process.platform !== "win32")("kills timed-out runtime command groups", async () => {
553+const runtimeSmoke = await import(pathToFileURL(runtimeSmokePath).href);
554+const root = makePackageRoot();
555+const commandPath = path.join(root, "timeout-command.mjs");
556+const descendantPidPath = path.join(root, "timed-out-descendant.pid");
557+const descendantScript = [
558+"import fs from 'node:fs';",
559+`fs.writeFileSync(${JSON.stringify(descendantPidPath)}, String(process.pid));`,
560+"process.on('SIGTERM', () => {});",
561+"setInterval(() => {}, 1000);",
562+].join("\n");
563+fs.writeFileSync(
564+commandPath,
565+[
566+"import childProcess from 'node:child_process';",
567+`childProcess.spawn(process.execPath, ["--input-type=module", "--eval", ${JSON.stringify(
568+ descendantScript,
569+ )}], { stdio: "ignore" });`,
570+"setInterval(() => {}, 1000);",
571+"",
572+].join("\n"),
573+"utf8",
574+);
575+576+let descendantPid: number | undefined;
577+try {
578+const commandResult = runtimeSmoke
579+.runCommand(process.execPath, [commandPath], { detached: undefined, timeoutMs: 1000 })
580+.catch((error: unknown) => error);
581+await waitForFile(descendantPidPath, 1000);
582+descendantPid = Number(fs.readFileSync(descendantPidPath, "utf8"));
583+const error = await commandResult;
584+if (!(error instanceof Error)) {
585+throw new Error("expected runtime command to time out");
586+}
587+expect(error.message).toMatch(/timed out after 1000ms/u);
588+589+await waitForDead(descendantPid, 2000);
590+} finally {
591+if (descendantPid !== undefined && pidIsAlive(descendantPid)) {
592+process.kill(descendantPid, "SIGKILL");
593+}
594+}
595+});
596+597+it.runIf(process.platform !== "win32")(
598+"falls back to direct kills for non-detached command timeouts",
599+async () => {
600+const runtimeSmoke = await import(pathToFileURL(runtimeSmokePath).href);
601+const root = makePackageRoot();
602+const commandPath = path.join(root, "non-detached-timeout-command.mjs");
603+const commandPidPath = path.join(root, "non-detached-command.pid");
604+fs.writeFileSync(
605+commandPath,
606+[
607+"import fs from 'node:fs';",
608+`fs.writeFileSync(${JSON.stringify(commandPidPath)}, String(process.pid));`,
609+"setInterval(() => {}, 1000);",
610+"",
611+].join("\n"),
612+"utf8",
613+);
614+615+let commandPid: number | undefined;
616+try {
617+const commandResult = runtimeSmoke
618+.runCommand(process.execPath, [commandPath], { detached: false, timeoutMs: 100 })
619+.catch((error: unknown) => error);
620+await waitForFile(commandPidPath, 1000);
621+commandPid = Number(fs.readFileSync(commandPidPath, "utf8"));
622+const error = await Promise.race([
623+commandResult,
624+new Promise<Error>((resolve) => {
625+setTimeout(() => {
626+resolve(new Error("runCommand did not settle after timeout"));
627+}, 2000);
628+}),
629+]);
630+if (!(error instanceof Error)) {
631+throw new Error("expected non-detached runtime command to time out");
632+}
633+expect(error.message).toMatch(/timed out after 100ms/u);
634+635+await waitForDead(commandPid, 1000);
636+} finally {
637+if (commandPid !== undefined && pidIsAlive(commandPid)) {
638+process.kill(commandPid, "SIGKILL");
639+}
640+}
641+},
642+);
643+644+it.runIf(process.platform !== "win32")(
645+"cleans detached runtime command groups when the parent is signaled",
646+async () => {
647+const root = makePackageRoot();
648+const commandPath = path.join(root, "signaled-command.mjs");
649+const runnerPath = path.join(root, "run-runtime-command.mjs");
650+const descendantPidPath = path.join(root, "command-descendant.pid");
651+const descendantScript = [
652+"import fs from 'node:fs';",
653+`fs.writeFileSync(${JSON.stringify(descendantPidPath)}, String(process.pid));`,
654+"process.on('SIGTERM', () => {});",
655+"setInterval(() => {}, 1000);",
656+].join("\n");
657+fs.writeFileSync(
658+commandPath,
659+[
660+"import childProcess from 'node:child_process';",
661+`childProcess.spawn(process.execPath, ["--input-type=module", "--eval", ${JSON.stringify(
662+ descendantScript,
663+ )}], { stdio: "ignore" });`,
664+"setInterval(() => {}, 1000);",
665+"",
666+].join("\n"),
667+"utf8",
668+);
669+fs.writeFileSync(
670+runnerPath,
671+[
672+`const runtimeSmoke = await import(${JSON.stringify(pathToFileURL(runtimeSmokePath).href)});`,
673+`void runtimeSmoke.runCommand(process.execPath, [${JSON.stringify(commandPath)}], {`,
674+" timeoutMs: 60_000,",
675+"}).catch(() => undefined);",
676+"setInterval(() => {}, 1000);",
677+"",
678+].join("\n"),
679+"utf8",
680+);
681+682+const runner = spawn(process.execPath, [runnerPath], {
683+stdio: "ignore",
684+});
685+let descendantPid: number | undefined;
686+try {
687+await waitForFile(descendantPidPath, 1000);
688+descendantPid = Number(fs.readFileSync(descendantPidPath, "utf8"));
689+expect(pidIsAlive(descendantPid)).toBe(true);
690+691+runner.kill("SIGTERM");
692+693+await waitForDead(descendantPid, 2000);
694+} finally {
695+if (runner.pid && pidIsAlive(runner.pid)) {
696+runner.kill("SIGKILL");
697+}
698+if (descendantPid !== undefined && pidIsAlive(descendantPid)) {
699+process.kill(descendantPid, "SIGKILL");
700+}
701+}
702+},
703+);
704+552705it.runIf(process.platform !== "win32")(
553706"cleans detached runtime gateway groups when the parent is signaled",
554707async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。