Fix dev server Ctrl-C shutdown wait
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
@@ -12,34 +12,44 @@ const inspectArg = process.env.PASEO_NODE_INSPECT ?? "--inspect";
|
|||||||
const inspectArgs =
|
const inspectArgs =
|
||||||
inspectArg === "0" || inspectArg === "false" || inspectArg === "off" ? [] : [inspectArg];
|
inspectArg === "0" || inspectArg === "false" || inspectArg === "off" ? [] : [inspectArg];
|
||||||
|
|
||||||
// The supervisor handles SIGINT/SIGTERM itself and needs time to drain the
|
const supervisorArgs = [
|
||||||
// worker gracefully. Ignore them here so spawnSync blocks until the supervisor
|
...inspectArgs,
|
||||||
// finishes shutting down, instead of the parent dying on Ctrl-C and releasing
|
"--heapsnapshot-near-heap-limit=3",
|
||||||
// the shell while the daemon is still logging.
|
"--max-old-space-size=3072",
|
||||||
process.on("SIGINT", () => {});
|
"--report-on-fatalerror",
|
||||||
process.on("SIGTERM", () => {});
|
"--report-directory=/tmp/paseo-reports",
|
||||||
|
...process.execArgv,
|
||||||
|
daemonRunnerEntry,
|
||||||
|
"--dev",
|
||||||
|
...process.argv.slice(2),
|
||||||
|
];
|
||||||
|
|
||||||
const result = spawnSync(
|
const supervisor = spawn(process.execPath, supervisorArgs, {
|
||||||
process.execPath,
|
stdio: "inherit",
|
||||||
[
|
env: process.env,
|
||||||
...inspectArgs,
|
});
|
||||||
"--heapsnapshot-near-heap-limit=3",
|
|
||||||
"--max-old-space-size=3072",
|
|
||||||
"--report-on-fatalerror",
|
|
||||||
"--report-directory=/tmp/paseo-reports",
|
|
||||||
...process.execArgv,
|
|
||||||
daemonRunnerEntry,
|
|
||||||
"--dev",
|
|
||||||
...process.argv.slice(2),
|
|
||||||
],
|
|
||||||
{
|
|
||||||
stdio: "inherit",
|
|
||||||
env: process.env,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.error) {
|
function exitCodeForSignal(signal: NodeJS.Signals): number {
|
||||||
throw result.error;
|
return signal === "SIGINT" ? 130 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(result.status ?? 1);
|
function forwardSignal(signal: NodeJS.Signals): void {
|
||||||
|
if (supervisor.exitCode !== null || supervisor.signalCode !== null || supervisor.killed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
supervisor.kill(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The supervisor handles SIGINT/SIGTERM itself and needs time to drain the
|
||||||
|
// worker gracefully. Keep this wrapper alive until the supervisor exits so npm
|
||||||
|
// does not release the shell while the daemon is still logging final shutdown.
|
||||||
|
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
||||||
|
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
||||||
|
|
||||||
|
supervisor.on("error", (error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
supervisor.on("exit", (code, signal) => {
|
||||||
|
process.exit(code ?? (signal ? exitCodeForSignal(signal) : 1));
|
||||||
|
});
|
||||||
|
|||||||
@@ -14,4 +14,12 @@ describe("supervision parity", () => {
|
|||||||
|
|
||||||
expect(daemonRunnerCalls + devRunnerCalls).toBe(1);
|
expect(daemonRunnerCalls + devRunnerCalls).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("dev runner waits asynchronously for supervisor shutdown", () => {
|
||||||
|
const devRunner = readFileSync(new URL("./dev-runner.ts", import.meta.url), "utf8");
|
||||||
|
|
||||||
|
expect(devRunner).toContain('import { spawn } from "node:child_process"');
|
||||||
|
expect(devRunner).not.toContain("spawnSync");
|
||||||
|
expect(devRunner).toContain('supervisor.on("exit"');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user