diff --git a/src/infra/restart.deferral-timeout.test.ts b/src/infra/restart.deferral-timeout.test.ts index 5eacc475016..80f20306494 100644 --- a/src/infra/restart.deferral-timeout.test.ts +++ b/src/infra/restart.deferral-timeout.test.ts @@ -66,6 +66,23 @@ describe("deferGatewayRestartUntilIdle timeout", () => { expect(hooks.onTimeout).toHaveBeenCalledOnce(); }); + it("clamps oversized poll intervals instead of polling immediately", () => { + const hooks: RestartDeferralHooks = { + onReady: vi.fn(), + }; + let pending = 1; + + deferGatewayRestartUntilIdle({ + getPendingCount: () => pending, + pollMs: Number.MAX_SAFE_INTEGER, + hooks, + }); + + pending = 0; + vi.advanceTimersByTime(1); + expect(hooks.onReady).not.toHaveBeenCalled(); + }); + it("carries timeout restart intent when the deferral budget is exhausted", () => { const hooks: RestartDeferralHooks = { onTimeout: vi.fn(), diff --git a/src/infra/restart.ts b/src/infra/restart.ts index 9b2c50594f9..271bcf5766e 100644 --- a/src/infra/restart.ts +++ b/src/infra/restart.ts @@ -9,6 +9,7 @@ import { resolveGatewaySystemdServiceName, } from "../daemon/constants.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; import { replaceFileAtomicSync } from "./replace-file.js"; import { cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } from "./restart-stale-pids.js"; import type { RestartAttempt } from "./restart.types.js"; @@ -494,8 +495,7 @@ export function deferGatewayRestartUntilIdle(opts: { reason?: string; timeoutIntent?: GatewayRestartIntent; }): void { - const pollMsRaw = opts.pollMs ?? DEFAULT_DEFERRAL_POLL_MS; - const pollMs = Math.max(10, Math.floor(pollMsRaw)); + const pollMs = resolveTimerTimeoutMs(opts.pollMs, DEFAULT_DEFERRAL_POLL_MS, 10); const maxWaitMs = typeof opts.maxWaitMs === "number" && Number.isFinite(opts.maxWaitMs) && opts.maxWaitMs > 0 ? Math.max(pollMs, Math.floor(opts.maxWaitMs))