mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
fix(gate): close probe connections before releasing the lane (#1229)
The gate-lane lock holds its lane by binding a local TCP port. Its server had no connection handler, so an idle local connection to that port (a port probe, `nc`, a monitor) kept `server.close()` waiting: the gate couldn't finish releasing its lane, and couldn't exit, until that connection dropped. The server now destroys each accepted socket at once. The bind is still the lock, so a live owner is never released early and a waiter still acquires the lane after release, now promptly. A test holds the lane, opens and keeps a client connection, and checks that release completes and the lane can be acquired again.
This commit is contained in:
1 parent
6d5f6c54ab
commit
bf5d2b5f72
2 files changed
+38
-1
No files matched your search
@@ -34,7 +34,9 @@ export async function acquireGateLane({ port = GATE_LANE_PORT, holderInfoPath })
|
||||
return { ok: false, reason: "bind-error", message: "Gate lane port must be an integer from 1 to 65535." };
|
||||
}
|
||||
// P1 + P2: bind 127.0.0.1 explicitly; do NOT pass reusePort — exclusivity is the mutex.
|
||||
const server = net.createServer();
|
||||
// This port names a mutex, not a service. A local probe must not leave a
|
||||
// connection open that keeps server.close() and gate release waiting.
|
||||
const server = net.createServer((socket) => socket.destroy());
|
||||
const bound = await new Promise((resolve) => {
|
||||
server.once("error", (err) => resolve({ ok: false, err }));
|
||||
server.listen(port, "127.0.0.1", () => resolve({ ok: true }));
|
||||
|
||||
@@ -124,3 +124,38 @@ test("configured gate port does not accept a partial numeric prefix", () => {
|
||||
`], { env: { ...process.env, OPENRIG_GATE_LANE_PORT: "40404typo" }, encoding: "utf8" });
|
||||
assert.deepEqual(JSON.parse(stdout), { finite: false, ok: false, reason: "bind-error" });
|
||||
});
|
||||
|
||||
|
||||
test("a local probe connection cannot delay gate release", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "openrig-lock-probe-"));
|
||||
const reservation = net.createServer();
|
||||
await new Promise((resolve) => reservation.listen(0, "127.0.0.1", resolve));
|
||||
const port = reservation.address().port;
|
||||
await new Promise((resolve) => reservation.close(resolve));
|
||||
const lane = await acquireGateLane({ port, holderInfoPath: join(root, "holder.json") });
|
||||
assert.equal(lane.ok, true);
|
||||
const client = net.connect(port, "127.0.0.1");
|
||||
client.on("error", () => {});
|
||||
let release;
|
||||
let timer;
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
client.once("connect", resolve);
|
||||
client.once("error", reject);
|
||||
});
|
||||
release = lane.release();
|
||||
const completed = await Promise.race([
|
||||
release.then(() => true),
|
||||
new Promise((resolve) => { timer = setTimeout(() => resolve(false), 500); }),
|
||||
]);
|
||||
assert.equal(completed, true, "an idle local connection must not keep the mutex owner alive");
|
||||
const next = await acquireGateLane({ port, holderInfoPath: join(root, "holder.json") });
|
||||
assert.equal(next.ok, true);
|
||||
if (next.ok) await next.release();
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
client.destroy();
|
||||
await (release ?? lane.release());
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user