From 935f078a89f0500024419c08b5f5c58e3743b987 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 3 May 2026 15:52:53 +0100 Subject: [PATCH] perf(gateway): skip force port scan when free --- src/cli/ports.ts | 4 ++++ src/cli/program.force.test.ts | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/cli/ports.ts b/src/cli/ports.ts index 3555db02096..1ee1de8e15e 100644 --- a/src/cli/ports.ts +++ b/src/cli/ports.ts @@ -266,6 +266,10 @@ export async function forceFreePortAndWait( let killed: PortProcess[] = []; let useFuserFallback = false; + if (!(await isPortBusy(port))) { + return { killed, waitedMs: 0, escalatedToSigkill: false }; + } + try { killed = forceFreePort(port); } catch (err) { diff --git a/src/cli/program.force.test.ts b/src/cli/program.force.test.ts index 8b4f291b4e8..fb96d6a4300 100644 --- a/src/cli/program.force.test.ts +++ b/src/cli/program.force.test.ts @@ -32,6 +32,9 @@ describe("gateway --force helpers", () => { originalKill = process.kill.bind(process); originalPlatform = process.platform; tryListenOnPortMock.mockReset(); + tryListenOnPortMock.mockRejectedValue( + Object.assign(new Error("in use"), { code: "EADDRINUSE" }), + ); // Pin to linux so all lsof tests are platform-invariant. Object.defineProperty(process, "platform", { value: "linux", configurable: true }); }); @@ -59,12 +62,8 @@ describe("gateway --force helpers", () => { expect(listPortListeners(18789)).toEqual([]); }); - it("does not re-scan lsof when no listeners were killed", async () => { - (execFileSync as unknown as Mock).mockImplementation(() => { - const err = new Error("no matches") as NodeJS.ErrnoException & { status?: number }; - err.status = 1; // lsof uses exit 1 for no matches - throw err; - }); + it("skips lsof when the port is already bindable", async () => { + tryListenOnPortMock.mockResolvedValue(undefined); const result = await forceFreePortAndWait(18789, { timeoutMs: 500, intervalMs: 100 }); @@ -73,7 +72,7 @@ describe("gateway --force helpers", () => { waitedMs: 0, escalatedToSigkill: false, }); - expect(execFileSync).toHaveBeenCalledOnce(); + expect(execFileSync).not.toHaveBeenCalled(); }); it("throws when lsof missing", () => { @@ -181,7 +180,9 @@ describe("gateway --force helpers", () => { } return "18789/tcp: 4242\n"; }); - tryListenOnPortMock.mockResolvedValue(undefined); + tryListenOnPortMock + .mockRejectedValueOnce(Object.assign(new Error("in use"), { code: "EADDRINUSE" })) + .mockResolvedValue(undefined); const result = await forceFreePortAndWait(18789, { timeoutMs: 500, intervalMs: 100 }); @@ -216,6 +217,7 @@ describe("gateway --force helpers", () => { .mockRejectedValueOnce(busyErr) .mockRejectedValueOnce(busyErr) .mockRejectedValueOnce(busyErr) + .mockRejectedValueOnce(busyErr) .mockResolvedValueOnce(undefined); const promise = forceFreePortAndWait(18789, {