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 { 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"');
});
}); });