From 5ad27fa25f730309407d4af8de1a7f693c8f3e20 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 5 Apr 2026 18:20:19 +0100 Subject: [PATCH] fix: allow slower Windows gateway restart health --- src/cli/daemon-cli/restart-health.test.ts | 28 +++++++++++++++++++++++ src/cli/daemon-cli/restart-health.ts | 13 ++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/cli/daemon-cli/restart-health.test.ts b/src/cli/daemon-cli/restart-health.test.ts index 24595639408..bc4a4d7c5db 100644 --- a/src/cli/daemon-cli/restart-health.test.ts +++ b/src/cli/daemon-cli/restart-health.test.ts @@ -278,6 +278,34 @@ describe("inspectGatewayRestart", () => { expect(sleep).toHaveBeenCalledTimes(25); }); + it("waits longer before stopped-free early exit on Windows", async () => { + Object.defineProperty(process, "platform", { value: "win32", configurable: true }); + const service = makeGatewayService({ status: "stopped" }); + inspectPortUsage.mockResolvedValue({ + port: 18789, + status: "free", + listeners: [], + hints: [], + }); + + const { waitForGatewayHealthyRestart } = await import("./restart-health.js"); + const snapshot = await waitForGatewayHealthyRestart({ + service, + port: 18789, + attempts: 120, + delayMs: 500, + }); + + expect(snapshot).toMatchObject({ + healthy: false, + runtime: { status: "stopped" }, + portUsage: { status: "free" }, + waitOutcome: "stopped-free", + elapsedMs: 27_500, + }); + expect(sleep).toHaveBeenCalledTimes(55); + }); + it("annotates timeout waits when the health loop exhausts all attempts", async () => { const service = makeGatewayService({ status: "running", pid: 8000 }); inspectPortUsage.mockResolvedValue({ diff --git a/src/cli/daemon-cli/restart-health.ts b/src/cli/daemon-cli/restart-health.ts index 6b94321d671..90e3b7fcd80 100644 --- a/src/cli/daemon-cli/restart-health.ts +++ b/src/cli/daemon-cli/restart-health.ts @@ -15,6 +15,8 @@ export const DEFAULT_RESTART_HEALTH_DELAY_MS = 500; export const DEFAULT_RESTART_HEALTH_ATTEMPTS = Math.ceil( DEFAULT_RESTART_HEALTH_TIMEOUT_MS / DEFAULT_RESTART_HEALTH_DELAY_MS, ); +const STOPPED_FREE_EARLY_EXIT_GRACE_MS = 10_000; +const WINDOWS_STOPPED_FREE_EARLY_EXIT_GRACE_MS = 25_000; export type GatewayRestartWaitOutcome = "healthy" | "stale-pids" | "stopped-free" | "timeout"; @@ -217,6 +219,12 @@ function shouldEarlyExitStoppedFree( ); } +function stoppedFreeEarlyExitGraceMs(): number { + return process.platform === "win32" + ? WINDOWS_STOPPED_FREE_EARLY_EXIT_GRACE_MS + : STOPPED_FREE_EARLY_EXIT_GRACE_MS; +} + function withWaitContext( snapshot: GatewayRestartSnapshot, waitOutcome: GatewayRestartWaitOutcome, @@ -245,7 +253,10 @@ export async function waitForGatewayHealthyRestart(params: { let consecutiveStoppedFreeCount = 0; const STOPPED_FREE_THRESHOLD = 6; - const minAttemptForEarlyExit = Math.min(Math.ceil(10_000 / delayMs), Math.floor(attempts / 2)); + const minAttemptForEarlyExit = Math.min( + Math.ceil(stoppedFreeEarlyExitGraceMs() / delayMs), + Math.floor(attempts / 2), + ); for (let attempt = 0; attempt < attempts; attempt += 1) { if (snapshot.healthy) {