fix(gateway): probe port liveness for stale lock recovery
Co-authored-by: Operative-001 <261882263+Operative-001@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayBonjourBeacon } from "../../infra/bonjour-discovery.js";
|
||||
import { pickBeaconHost, pickGatewayPort } from "./discover.js";
|
||||
|
||||
const acquireGatewayLock = vi.fn(async () => ({
|
||||
const acquireGatewayLock = vi.fn(async (_opts?: { port?: number }) => ({
|
||||
release: vi.fn(async () => {}),
|
||||
}));
|
||||
const consumeGatewaySigusr1RestartAuthorization = vi.fn(() => true);
|
||||
@@ -22,7 +22,7 @@ const gatewayLog = {
|
||||
};
|
||||
|
||||
vi.mock("../../infra/gateway-lock.js", () => ({
|
||||
acquireGatewayLock: () => acquireGatewayLock(),
|
||||
acquireGatewayLock: (opts?: { port?: number }) => acquireGatewayLock(opts),
|
||||
}));
|
||||
|
||||
vi.mock("../../infra/restart.js", () => ({
|
||||
@@ -109,12 +109,17 @@ function createSignaledStart(close: GatewayCloseFn) {
|
||||
return { start, started };
|
||||
}
|
||||
|
||||
async function runLoopWithStart(params: { start: ReturnType<typeof vi.fn>; runtime: LoopRuntime }) {
|
||||
async function runLoopWithStart(params: {
|
||||
start: ReturnType<typeof vi.fn>;
|
||||
runtime: LoopRuntime;
|
||||
lockPort?: number;
|
||||
}) {
|
||||
vi.resetModules();
|
||||
const { runGatewayLoop } = await import("./run-loop.js");
|
||||
const loopPromise = runGatewayLoop({
|
||||
start: params.start as unknown as Parameters<typeof runGatewayLoop>[0]["start"],
|
||||
runtime: params.runtime,
|
||||
lockPort: params.lockPort,
|
||||
});
|
||||
return { loopPromise };
|
||||
}
|
||||
@@ -276,6 +281,39 @@ describe("runGatewayLoop", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards lockPort to initial and restart lock acquisitions", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
await withIsolatedSignals(async () => {
|
||||
const closeFirst = vi.fn(async () => {});
|
||||
const closeSecond = vi.fn(async () => {});
|
||||
restartGatewayProcessWithFreshPid.mockReturnValueOnce({ mode: "disabled" });
|
||||
|
||||
const start = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ close: closeFirst })
|
||||
.mockResolvedValueOnce({ close: closeSecond })
|
||||
.mockRejectedValueOnce(new Error("stop-loop"));
|
||||
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
|
||||
const { runGatewayLoop } = await import("./run-loop.js");
|
||||
const loopPromise = runGatewayLoop({
|
||||
start: start as unknown as Parameters<typeof runGatewayLoop>[0]["start"],
|
||||
runtime: runtime as unknown as Parameters<typeof runGatewayLoop>[0]["runtime"],
|
||||
lockPort: 18789,
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
process.emit("SIGUSR1");
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
process.emit("SIGUSR1");
|
||||
|
||||
await expect(loopPromise).rejects.toThrow("stop-loop");
|
||||
expect(acquireGatewayLock).toHaveBeenNthCalledWith(1, { port: 18789 });
|
||||
expect(acquireGatewayLock).toHaveBeenNthCalledWith(2, { port: 18789 });
|
||||
expect(acquireGatewayLock).toHaveBeenNthCalledWith(3, { port: 18789 });
|
||||
});
|
||||
});
|
||||
|
||||
it("exits when lock reacquire fails during in-process restart fallback", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
|
||||
@@ -22,8 +22,9 @@ type GatewayRunSignalAction = "stop" | "restart";
|
||||
export async function runGatewayLoop(params: {
|
||||
start: () => Promise<Awaited<ReturnType<typeof startGatewayServer>>>;
|
||||
runtime: typeof defaultRuntime;
|
||||
lockPort?: number;
|
||||
}) {
|
||||
let lock = await acquireGatewayLock();
|
||||
let lock = await acquireGatewayLock({ port: params.lockPort });
|
||||
let server: Awaited<ReturnType<typeof startGatewayServer>> | null = null;
|
||||
let shuttingDown = false;
|
||||
let restartResolver: (() => void) | null = null;
|
||||
@@ -47,7 +48,7 @@ export async function runGatewayLoop(params: {
|
||||
};
|
||||
const reacquireLockForInProcessRestart = async (): Promise<boolean> => {
|
||||
try {
|
||||
lock = await acquireGatewayLock();
|
||||
lock = await acquireGatewayLock({ port: params.lockPort });
|
||||
return true;
|
||||
} catch (err) {
|
||||
gatewayLog.error(`failed to reacquire gateway lock for in-process restart: ${String(err)}`);
|
||||
|
||||
@@ -317,6 +317,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
||||
try {
|
||||
await runGatewayLoop({
|
||||
runtime: defaultRuntime,
|
||||
lockPort: port,
|
||||
start: async () =>
|
||||
await startGatewayServer(port, {
|
||||
bind,
|
||||
|
||||
Reference in New Issue
Block a user