Fix dev server Ctrl-C shutdown wait

This commit is contained in:
Mohamed Boudra
2026-04-22 12:03:46 +07:00
parent 6503245493
commit 77c8c91ab5
2 changed files with 46 additions and 28 deletions
+38 -28
View File
@@ -1,4 +1,4 @@
import { spawnSync } from "node:child_process";
import { spawn } from "node:child_process";
import { fileURLToPath } from "url";
import dotenv from "dotenv";
@@ -12,34 +12,44 @@ const inspectArg = process.env.PASEO_NODE_INSPECT ?? "--inspect";
const inspectArgs =
inspectArg === "0" || inspectArg === "false" || inspectArg === "off" ? [] : [inspectArg];
// The supervisor handles SIGINT/SIGTERM itself and needs time to drain the
// worker gracefully. Ignore them here so spawnSync blocks until the supervisor
// finishes shutting down, instead of the parent dying on Ctrl-C and releasing
// the shell while the daemon is still logging.
process.on("SIGINT", () => {});
process.on("SIGTERM", () => {});
const supervisorArgs = [
...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),
];
const result = spawnSync(
process.execPath,
[
...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,
},
);
const supervisor = spawn(process.execPath, supervisorArgs, {
stdio: "inherit",
env: process.env,
});
if (result.error) {
throw result.error;
function exitCodeForSignal(signal: NodeJS.Signals): number {
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);
});
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"');
});
});