Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7c2646dcf |
No files matched your search
@@ -272,6 +272,41 @@ describe.skipIf(process.platform === "win32")(
|
||||
}
|
||||
});
|
||||
|
||||
it("settles after the grace when an exited command's descendant holds its output open", async () => {
|
||||
const directory = await createTestTempDirectory("rea-exec-descendant-");
|
||||
const pidFile = join(directory, "descendant.pid");
|
||||
// The command prints, then exits at once, leaving a descendant that
|
||||
// inherited stdout and stderr and keeps both pipes open for 60 s.
|
||||
const script = [
|
||||
'const { spawn } = require("node:child_process");',
|
||||
'const fs = require("node:fs");',
|
||||
'const child = spawn(process.execPath, ["-e", "setTimeout(() => {}, 60000)"], { stdio: ["ignore", "inherit", "inherit"] });',
|
||||
`fs.writeFileSync(${JSON.stringify(pidFile)}, String(child.pid));`,
|
||||
"child.unref();",
|
||||
'process.stdout.write("kuna 1.833\\n");',
|
||||
].join("\n");
|
||||
const started = Date.now();
|
||||
try {
|
||||
await expect(
|
||||
execFileOutput(process.execPath, ["-e", script], {
|
||||
timeout: 500,
|
||||
stopSignal: "SIGTERM",
|
||||
}),
|
||||
).rejects.toMatchObject({ code: "ETIMEDOUT", killed: true });
|
||||
// Deadline plus the 5 s stop grace, with scheduling slack.
|
||||
expect(Date.now() - started).toBeLessThan(15_000);
|
||||
} finally {
|
||||
const pid = Number(await readFile(pidFile, "utf8").catch(() => ""));
|
||||
if (Number.isSafeInteger(pid) && pid > 0)
|
||||
try {
|
||||
process.kill(pid, "SIGKILL");
|
||||
} catch {
|
||||
// The descendant may already have exited.
|
||||
}
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
it("kills a timed-out process that ignores the stop signal", async () => {
|
||||
const script = [
|
||||
'for (const name of ["SIGINT", "SIGTERM"]) process.on(name, () => {});',
|
||||
|
||||
@@ -186,7 +186,14 @@ export const execFileBytes = (
|
||||
if (stopped !== undefined) return;
|
||||
stopped = reason;
|
||||
child.kill(stopSignal);
|
||||
grace = setTimeout(() => child.kill("SIGKILL"), STOP_GRACE_MS);
|
||||
grace = setTimeout(() => {
|
||||
child.kill("SIGKILL");
|
||||
// execFile settles only once every output pipe closes. A descendant
|
||||
// that inherited them can hold them open after the command exits, so
|
||||
// stop reading at the end of the grace instead of waiting for it.
|
||||
child.stdout?.destroy();
|
||||
child.stderr?.destroy();
|
||||
}, STOP_GRACE_MS);
|
||||
};
|
||||
const onAbort = (): void => {
|
||||
stop("abort");
|
||||
|
||||
Reference in new issue
Block a user