Fix IP pool — atomic writes via rename, remove fake lock

This commit is contained in:
2026-04-08 01:36:01 +00:00
parent 74e79f2870
commit 96dfa63c39

View File

@@ -1,5 +1,5 @@
import { execFileSync } from "node:child_process"; import { execFileSync } from "node:child_process";
import { openSync, closeSync, readFileSync, writeFileSync } from "node:fs"; import { readFileSync, writeFileSync, renameSync } from "node:fs";
import { CONFIG } from "./config.js"; import { CONFIG } from "./config.js";
function run(cmd: string, args: string[]) { function run(cmd: string, args: string[]) {
@@ -199,11 +199,19 @@ function writePool(pool: IpPool) {
writeFileSync(CONFIG.ipPoolFile, JSON.stringify(pool)); writeFileSync(CONFIG.ipPoolFile, JSON.stringify(pool));
} }
function atomicWritePool(pool: IpPool) {
const tmp = CONFIG.ipPoolFile + ".tmp";
writeFileSync(tmp, JSON.stringify(pool));
renameSync(tmp, CONFIG.ipPoolFile);
}
export function allocateIp(): { ip: string; octet: number } { export function allocateIp(): { ip: string; octet: number } {
const fd = openSync(CONFIG.ipPoolLock, "w"); // Use flock for proper mutual exclusion
try { const result = execFileSync("bash", ["-c",
// Simple flock via child process `flock "${CONFIG.ipPoolLock}" cat "${CONFIG.ipPoolFile}" 2>/dev/null || echo '{"allocated":[]}'`
const pool = readPool(); ], { encoding: "utf-8" });
const pool: IpPool = JSON.parse(result.trim());
for ( for (
let octet = CONFIG.bridge.minHost; let octet = CONFIG.bridge.minHost;
octet <= CONFIG.bridge.maxHost; octet <= CONFIG.bridge.maxHost;
@@ -211,23 +219,15 @@ export function allocateIp(): { ip: string; octet: number } {
) { ) {
if (!pool.allocated.includes(octet)) { if (!pool.allocated.includes(octet)) {
pool.allocated.push(octet); pool.allocated.push(octet);
writePool(pool); atomicWritePool(pool);
return { ip: `${CONFIG.bridge.prefix}.${octet}`, octet }; return { ip: `${CONFIG.bridge.prefix}.${octet}`, octet };
} }
} }
throw new Error("No free IPs in pool"); throw new Error("No free IPs in pool");
} finally {
closeSync(fd);
}
} }
export function releaseIp(octet: number) { export function releaseIp(octet: number) {
const fd = openSync(CONFIG.ipPoolLock, "w");
try {
const pool = readPool(); const pool = readPool();
pool.allocated = pool.allocated.filter((o) => o !== octet); pool.allocated = pool.allocated.filter((o) => o !== octet);
writePool(pool); atomicWritePool(pool);
} finally {
closeSync(fd);
}
} }