Spaces:
Paused
Paused
File size: 4,157 Bytes
c1243f9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | import { describe, expect, it, vi } from "vitest";
const acquireGatewayLock = vi.fn(async () => ({
release: vi.fn(async () => {}),
}));
const consumeGatewaySigusr1RestartAuthorization = vi.fn(() => true);
const isGatewaySigusr1RestartExternallyAllowed = vi.fn(() => false);
const markGatewaySigusr1RestartHandled = vi.fn();
const getActiveTaskCount = vi.fn(() => 0);
const waitForActiveTasks = vi.fn(async () => ({ drained: true }));
const resetAllLanes = vi.fn();
const DRAIN_TIMEOUT_LOG = "drain timeout reached; proceeding with restart";
const gatewayLog = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
vi.mock("../../infra/gateway-lock.js", () => ({
acquireGatewayLock: () => acquireGatewayLock(),
}));
vi.mock("../../infra/restart.js", () => ({
consumeGatewaySigusr1RestartAuthorization: () => consumeGatewaySigusr1RestartAuthorization(),
isGatewaySigusr1RestartExternallyAllowed: () => isGatewaySigusr1RestartExternallyAllowed(),
markGatewaySigusr1RestartHandled: () => markGatewaySigusr1RestartHandled(),
}));
vi.mock("../../process/command-queue.js", () => ({
getActiveTaskCount: () => getActiveTaskCount(),
waitForActiveTasks: (timeoutMs: number) => waitForActiveTasks(timeoutMs),
resetAllLanes: () => resetAllLanes(),
}));
vi.mock("../../logging/subsystem.js", () => ({
createSubsystemLogger: () => gatewayLog,
}));
function removeNewSignalListeners(
signal: NodeJS.Signals,
existing: Set<(...args: unknown[]) => void>,
) {
for (const listener of process.listeners(signal)) {
const fn = listener as (...args: unknown[]) => void;
if (!existing.has(fn)) {
process.removeListener(signal, fn);
}
}
}
describe("runGatewayLoop", () => {
it("restarts after SIGUSR1 even when drain times out, and resets lanes for the new iteration", async () => {
vi.clearAllMocks();
getActiveTaskCount.mockReturnValueOnce(2).mockReturnValueOnce(0);
waitForActiveTasks.mockResolvedValueOnce({ drained: false });
type StartServer = () => Promise<{
close: (opts: { reason: string; restartExpectedMs: number | null }) => Promise<void>;
}>;
const closeFirst = vi.fn(async () => {});
const closeSecond = vi.fn(async () => {});
const start = vi
.fn<StartServer>()
.mockResolvedValueOnce({ close: closeFirst })
.mockResolvedValueOnce({ close: closeSecond })
.mockRejectedValueOnce(new Error("stop-loop"));
const beforeSigterm = new Set(
process.listeners("SIGTERM") as Array<(...args: unknown[]) => void>,
);
const beforeSigint = new Set(
process.listeners("SIGINT") as Array<(...args: unknown[]) => void>,
);
const beforeSigusr1 = new Set(
process.listeners("SIGUSR1") as Array<(...args: unknown[]) => void>,
);
const loopPromise = import("./run-loop.js").then(({ runGatewayLoop }) =>
runGatewayLoop({
start,
runtime: {
exit: vi.fn(),
} as { exit: (code: number) => never },
}),
);
try {
await vi.waitFor(() => {
expect(start).toHaveBeenCalledTimes(1);
});
process.emit("SIGUSR1");
await vi.waitFor(() => {
expect(start).toHaveBeenCalledTimes(2);
});
expect(waitForActiveTasks).toHaveBeenCalledWith(30_000);
expect(gatewayLog.warn).toHaveBeenCalledWith(DRAIN_TIMEOUT_LOG);
expect(closeFirst).toHaveBeenCalledWith({
reason: "gateway restarting",
restartExpectedMs: 1500,
});
expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(1);
expect(resetAllLanes).toHaveBeenCalledTimes(1);
process.emit("SIGUSR1");
await expect(loopPromise).rejects.toThrow("stop-loop");
expect(closeSecond).toHaveBeenCalledWith({
reason: "gateway restarting",
restartExpectedMs: 1500,
});
expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(2);
expect(resetAllLanes).toHaveBeenCalledTimes(2);
} finally {
removeNewSignalListeners("SIGTERM", beforeSigterm);
removeNewSignalListeners("SIGINT", beforeSigint);
removeNewSignalListeners("SIGUSR1", beforeSigusr1);
}
});
});
|