From dbcbafc208a1e6f0a9b4b6ec5f1312853bb5bd9e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 17 Jun 2026 18:40:01 +0200 Subject: [PATCH] fix(scripts): wait after force killing rpc gateway --- scripts/measure-rpc-rtt.mjs | 3 +++ test/scripts/measure-rpc-rtt.test.ts | 39 ++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/scripts/measure-rpc-rtt.mjs b/scripts/measure-rpc-rtt.mjs index 1e6ed2a9b3c0..06ef32300683 100644 --- a/scripts/measure-rpc-rtt.mjs +++ b/scripts/measure-rpc-rtt.mjs @@ -16,6 +16,7 @@ const DEFAULT_ITERATIONS = 10; export const READY_TIMEOUT_MS = 120_000; /** Per-probe timeout used while polling gateway readiness endpoints. */ export const READY_PROBE_TIMEOUT_MS = 1_000; +const GATEWAY_FORCE_KILL_GRACE_MS = 250; const PARENT_TERMINATION_SIGNALS = ["SIGHUP", "SIGINT", "SIGTERM"]; const IS_DIRECT_RUN = typeof process.argv[1] === "string" && @@ -320,10 +321,12 @@ export async function stopGateway(child, options = {}) { return; } const killGraceMs = Math.max(0, options.killGraceMs ?? 1_500); + const forceKillGraceMs = Math.max(0, options.forceKillGraceMs ?? GATEWAY_FORCE_KILL_GRACE_MS); signalGatewayProcess(child, "SIGTERM", options.killProcess); const exited = await waitForGatewayExit(child, killGraceMs, options.killProcess); if (!exited) { signalGatewayProcess(child, "SIGKILL", options.killProcess); + await waitForGatewayExit(child, forceKillGraceMs, options.killProcess); } } diff --git a/test/scripts/measure-rpc-rtt.test.ts b/test/scripts/measure-rpc-rtt.test.ts index 3f4898ba2a79..1a596bc5e422 100644 --- a/test/scripts/measure-rpc-rtt.test.ts +++ b/test/scripts/measure-rpc-rtt.test.ts @@ -315,7 +315,42 @@ describe("scripts/measure-rpc-rtt.mjs", () => { } else { expect(kill).toHaveBeenNthCalledWith(1, -12346, 0); expect(kill).toHaveBeenNthCalledWith(2, -12346, "SIGTERM"); - expect(kill).toHaveBeenLastCalledWith(-12346, "SIGKILL"); + expect(kill).toHaveBeenCalledWith(-12346, "SIGKILL"); + expect(child.kill).not.toHaveBeenCalled(); + } + }); + + it("waits for the process group to disappear after force kill", async () => { + const child = Object.assign(new EventEmitter(), { + exitCode: null, + kill: vi.fn(), + pid: 12350, + signalCode: null, + }); + let sawForceKill = false; + let postKillLivenessChecks = 0; + const kill = vi.fn((_pid: number, signal: number | NodeJS.Signals) => { + if (signal === "SIGKILL") { + sawForceKill = true; + return true; + } + if (signal === 0 && sawForceKill) { + postKillLivenessChecks += 1; + if (postKillLivenessChecks >= 2) { + throw Object.assign(new Error("no such process"), { code: "ESRCH" }); + } + } + return true; + }); + + await stopGateway(child, { forceKillGraceMs: 50, killGraceMs: 1, killProcess: kill }); + + if (process.platform === "win32") { + expect(child.kill).toHaveBeenNthCalledWith(1, "SIGTERM"); + expect(child.kill).toHaveBeenNthCalledWith(2, "SIGKILL"); + } else { + expect(kill).toHaveBeenCalledWith(-12350, "SIGKILL"); + expect(postKillLivenessChecks).toBe(2); expect(child.kill).not.toHaveBeenCalled(); } }); @@ -336,7 +371,7 @@ describe("scripts/measure-rpc-rtt.mjs", () => { } else { expect(kill).toHaveBeenNthCalledWith(1, -12347, 0); expect(kill).toHaveBeenNthCalledWith(2, -12347, "SIGTERM"); - expect(kill).toHaveBeenLastCalledWith(-12347, "SIGKILL"); + expect(kill).toHaveBeenCalledWith(-12347, "SIGKILL"); expect(child.kill).not.toHaveBeenCalled(); } });