mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
fix(process): stop timed-out commands with the caller's stop signal
execFileOutput forwarded stopSignal only together with an AbortSignal, so a caller with just a timeout (the native UI helper's swiftc build) still got Node's SIGTERM. When the timeout did send the stop signal, nothing killed a process that ignored it, and a process that exited cleanly after the signal resolved as a success. A stoppable command now owns its timeout: it sends the stop signal, kills the process after the grace period, and rejects with ETIMEDOUT and killed: true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e6905c18e0
commit
063de5014f
2 files changed
+105
-20
No files matched your search
@@ -95,3 +95,50 @@ describe("execFileOutput", () => {
|
||||
).rejects.toMatchObject({ name: "AbortError" });
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(process.platform === "win32")(
|
||||
"execFileOutput stop signal at a timeout",
|
||||
() => {
|
||||
it("stops a timed-out process with the caller's signal and reports the timeout", async () => {
|
||||
const directory = await createTestTempDirectory("rea-exec-timeout-");
|
||||
const stopped = join(directory, "stopped");
|
||||
// The child exits cleanly once it has recorded the signal it received.
|
||||
const script = [
|
||||
'const fs = require("node:fs");',
|
||||
'for (const name of ["SIGINT", "SIGTERM"])',
|
||||
` process.on(name, () => setTimeout(() => { fs.writeFileSync(${JSON.stringify(stopped)}, name); process.exit(0); }, 200));`,
|
||||
"setInterval(() => {}, 1000);",
|
||||
].join("\n");
|
||||
|
||||
const failure: unknown = await execFileOutput(
|
||||
process.execPath,
|
||||
["-e", script],
|
||||
{ timeout: 2_000, stopSignal: "SIGINT" },
|
||||
).then(
|
||||
() => undefined,
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
|
||||
expect(failure).toMatchObject({ code: "ETIMEDOUT", killed: true });
|
||||
expect(await readFile(stopped, "utf8")).toBe("SIGINT");
|
||||
});
|
||||
|
||||
it("kills a timed-out process that ignores the stop signal", async () => {
|
||||
const script = [
|
||||
'for (const name of ["SIGINT", "SIGTERM"]) process.on(name, () => {});',
|
||||
"setInterval(() => {}, 1000);",
|
||||
].join("\n");
|
||||
|
||||
await expect(
|
||||
execFileOutput(process.execPath, ["-e", script], {
|
||||
timeout: 1_000,
|
||||
stopSignal: "SIGINT",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ETIMEDOUT",
|
||||
killed: true,
|
||||
signal: "SIGKILL",
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user