























@@ -1,4 +1,5 @@
11import { describe, expect, it, vi } from "vitest";
2+import type { GatewayServer } from "../../gateway/server.impl.js";
23import type { GatewayBonjourBeacon } from "../../infra/bonjour-discovery.js";
34import { pickBeaconHost, pickGatewayPort } from "./discover.js";
45@@ -249,13 +250,33 @@ function createRuntimeWithExitSignal(exitCallOrder?: string[]) {
249250return { runtime, exited };
250251}
251252252-type GatewayCloseFn = (...args: unknown[]) => Promise<void>;
253+type GatewayCloseFn = GatewayServer["close"];
253254type LoopRuntime = {
254255log: (...args: unknown[]) => void;
255256error: (...args: unknown[]) => void;
256257exit: (code: number) => void;
257258};
258259260+function createCloseMock() {
261+return vi.fn<GatewayCloseFn>(async (_opts) => {});
262+}
263+264+function expectRestartCloseCall(
265+close: ReturnType<typeof createCloseMock>,
266+maxDrainTimeoutMs: number,
267+) {
268+expect(close).toHaveBeenCalledWith(
269+expect.objectContaining({
270+reason: "gateway restarting",
271+restartExpectedMs: 1500,
272+drainTimeoutMs: expect.any(Number),
273+}),
274+);
275+const closeArgs = close.mock.calls[0]?.[0];
276+expect(closeArgs?.drainTimeoutMs).toBeLessThanOrEqual(maxDrainTimeoutMs);
277+expect(closeArgs?.drainTimeoutMs).toBeGreaterThanOrEqual(0);
278+}
279+259280function createSignaledStart(close: GatewayCloseFn) {
260281let resolveStarted: (() => void) | null = null;
261282const started = new Promise<void>((resolve) => {
@@ -304,7 +325,7 @@ async function waitForLoopCondition(predicate: () => boolean, message: string) {
304325}
305326306327async function createSignaledLoopHarness(exitCallOrder?: string[]) {
307-const close = vi.fn(async () => {});
328+const close = createCloseMock();
308329const { start, started } = createSignaledStart(close);
309330const { runtime, exited } = createRuntimeWithExitSignal(exitCallOrder);
310331const { loopPromise } = await runLoopWithStart({ start, runtime });
@@ -361,8 +382,8 @@ describe("runGatewayLoop", () => {
361382getActiveTaskCount.mockReturnValueOnce(1).mockReturnValue(0);
362383363384await withIsolatedSignals(async ({ captureSignal }) => {
364-const closeFirst = vi.fn(async () => {});
365-const closeSecond = vi.fn(async () => {});
385+const closeFirst = createCloseMock();
386+const closeSecond = createCloseMock();
366387const { runtime, exited } = createRuntimeWithExitSignal();
367388let resolveSecond: (() => void) | null = null;
368389const startedSecond = new Promise<void>((resolve) => {
@@ -391,10 +412,7 @@ describe("runGatewayLoop", () => {
391412expect(consumeGatewayRestartIntentPayloadSync).toHaveBeenCalledOnce();
392413expect(markGatewayDraining).toHaveBeenCalledOnce();
393414expect(waitForActiveTasks).toHaveBeenCalledWith(90_000);
394-expect(closeFirst).toHaveBeenCalledWith({
395-reason: "gateway restarting",
396-restartExpectedMs: 1500,
397-});
415+expectRestartCloseCall(closeFirst, 90_000);
398416await startedSecond;
399417expect(start).toHaveBeenCalledTimes(2);
400418await new Promise<void>((resolve) => setImmediate(resolve));
@@ -430,6 +448,27 @@ describe("runGatewayLoop", () => {
430448});
431449});
432450451+it("caps reply drain time for unbounded SIGTERM restarts", async () => {
452+vi.clearAllMocks();
453+consumeGatewayRestartIntentPayloadSync.mockReturnValueOnce({ waitMs: 0 });
454+455+await withIsolatedSignals(async ({ captureSignal }) => {
456+const { close, start, exited } = await createSignaledLoopHarness();
457+const sigterm = captureSignal("SIGTERM");
458+const sigint = captureSignal("SIGINT");
459+460+sigterm();
461+await new Promise<void>((resolve) => setImmediate(resolve));
462+await new Promise<void>((resolve) => setImmediate(resolve));
463+464+expectRestartCloseCall(close, 15_000);
465+expect(start).toHaveBeenCalledTimes(2);
466+467+sigint();
468+await expect(exited).resolves.toBe(0);
469+});
470+});
471+433472it("aborts active embedded runs after a short restart drain grace", async () => {
434473vi.clearAllMocks();
435474consumeGatewayRestartIntentPayloadSync.mockReturnValueOnce({});
@@ -473,10 +512,7 @@ describe("runGatewayLoop", () => {
473512expect(gatewayLog.warn).toHaveBeenCalledWith(
474513"failed to mark interrupted main sessions for restart recovery: Error: store read-only",
475514);
476-expect(close).toHaveBeenCalledWith({
477-reason: "gateway restarting",
478-restartExpectedMs: 1500,
479-});
515+expectRestartCloseCall(close, 90_000);
480516expect(start).toHaveBeenCalledTimes(2);
481517482518sigint();
@@ -567,12 +603,12 @@ describe("runGatewayLoop", () => {
567603waitForActiveEmbeddedRuns.mockResolvedValueOnce({ drained: true });
568604569605type StartServer = () => Promise<{
570-close: (opts: { reason: string; restartExpectedMs: number | null }) => Promise<void>;
606+close: GatewayCloseFn;
571607}>;
572608573-const closeFirst = vi.fn(async () => {});
574-const closeSecond = vi.fn(async () => {});
575-const closeThird = vi.fn(async () => {});
609+const closeFirst = createCloseMock();
610+const closeSecond = createCloseMock();
611+const closeThird = createCloseMock();
576612const { runtime, exited } = createRuntimeWithExitSignal();
577613578614const start = vi.fn<StartServer>();
@@ -639,10 +675,7 @@ describe("runGatewayLoop", () => {
639675});
640676expect(markGatewayDraining).toHaveBeenCalledTimes(1);
641677expect(gatewayLog.warn).toHaveBeenCalledWith(DRAIN_TIMEOUT_LOG);
642-expect(closeFirst).toHaveBeenCalledWith({
643-reason: "gateway restarting",
644-restartExpectedMs: 1500,
645-});
678+expectRestartCloseCall(closeFirst, 1_234);
646679expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(1);
647680expect(resetAllLanes).toHaveBeenCalledTimes(1);
648681expect(resetGatewayRestartStateForInProcessRestart).toHaveBeenCalledTimes(1);
@@ -652,10 +685,7 @@ describe("runGatewayLoop", () => {
652685653686await startedThird;
654687await new Promise<void>((resolve) => setImmediate(resolve));
655-expect(closeSecond).toHaveBeenCalledWith({
656-reason: "gateway restarting",
657-restartExpectedMs: 1500,
658-});
688+expectRestartCloseCall(closeSecond, 1_234);
659689expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(2);
660690expect(markGatewayDraining).toHaveBeenCalledTimes(2);
661691expect(resetAllLanes).toHaveBeenCalledTimes(2);
@@ -681,8 +711,8 @@ describe("runGatewayLoop", () => {
681711});
682712683713await withIsolatedSignals(async ({ captureSignal }) => {
684-const closeFirst = vi.fn(async () => {});
685-const closeSecond = vi.fn(async () => {});
714+const closeFirst = createCloseMock();
715+const closeSecond = createCloseMock();
686716const { runtime, exited } = createRuntimeWithExitSignal();
687717let releaseFirstStart!: () => void;
688718const firstStartMayReturn = new Promise<void>((resolve) => {
@@ -729,10 +759,7 @@ describe("runGatewayLoop", () => {
729759"expected queued SIGUSR1 to trigger the second gateway start",
730760);
731761await startedSecond;
732-expect(closeFirst).toHaveBeenCalledWith({
733-reason: "gateway restarting",
734-restartExpectedMs: 1500,
735-});
762+expectRestartCloseCall(closeFirst, 90_000);
736763expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(1);
737764expect(markGatewayDraining).toHaveBeenCalledTimes(1);
738765expect(resetAllLanes).toHaveBeenCalledTimes(1);
@@ -869,8 +896,8 @@ describe("runGatewayLoop", () => {
869896});
870897871898await withIsolatedSignals(async ({ captureSignal }) => {
872-const closeFirst = vi.fn(async () => {});
873-const closeThird = vi.fn(async () => {});
899+const closeFirst = createCloseMock();
900+const closeThird = createCloseMock();
874901const { runtime, exited } = createRuntimeWithExitSignal();
875902let sigusr1: (() => void) | null = null;
876903let resolveThirdStart: (() => void) | null = null;
@@ -909,10 +936,7 @@ describe("runGatewayLoop", () => {
909936"expected queued SIGUSR1 to advance past failed restart startup",
910937);
911938await startedThird;
912-expect(closeFirst).toHaveBeenCalledWith({
913-reason: "gateway restarting",
914-restartExpectedMs: 1500,
915-});
939+expectRestartCloseCall(closeFirst, 90_000);
916940expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(2);
917941expect(markGatewayDraining).toHaveBeenCalledTimes(2);
918942expect(resetAllLanes).toHaveBeenCalledTimes(2);
@@ -938,8 +962,8 @@ describe("runGatewayLoop", () => {
938962});
939963940964await withIsolatedSignals(async ({ captureSignal }) => {
941-const closeFirst = vi.fn(async () => {});
942-const closeThird = vi.fn(async () => {});
965+const closeFirst = createCloseMock();
966+const closeThird = createCloseMock();
943967const { runtime, exited } = createRuntimeWithExitSignal();
944968let resolveThirdStart: (() => void) | null = null;
945969const startedThird = new Promise<void>((resolve) => {
@@ -980,10 +1004,7 @@ describe("runGatewayLoop", () => {
9801004"expected post-failure SIGUSR1 to retry gateway startup",
9811005);
9821006await startedThird;
983-expect(closeFirst).toHaveBeenCalledWith({
984-reason: "gateway restarting",
985-restartExpectedMs: 1500,
986-});
1007+expectRestartCloseCall(closeFirst, 90_000);
9871008expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(2);
9881009expect(markGatewayDraining).toHaveBeenCalledTimes(2);
9891010expect(resetAllLanes).toHaveBeenCalledTimes(2);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。