seedproject-web/agents/scripts/lib/lock.mjs

47 lines
1.5 KiB
JavaScript
Raw Normal View History

// Generic exclusive file lock, same idiom as calendar.mjs but reusable for any
// critical section. Used for the global BUILD lock so cron builds (promoteDraft)
// and Developer Console builds (preview + publish) never run npm/astro
// concurrently — important on this memory-constrained box.
import { writeFileSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/**
* Acquire an exclusive lockfile, run fn, always release.
* @param {string} lockPath absolute path to the lockfile
* @param {() => Promise<T>|T} fn critical section
* @param {{timeoutMs?: number, staleMs?: number}} [opts]
* @returns {Promise<T>}
*/
export async function withLock(lockPath, fn, { timeoutMs = 300000, staleMs = 15 * 60 * 1000 } = {}) {
const start = Date.now();
for (;;) {
try {
const fd = openSync(lockPath, "wx"); // exclusive create — fails if it exists
writeFileSync(lockPath, `${process.pid} ${new Date().toISOString()}`);
closeSync(fd);
break;
} catch {
try {
if (Date.now() - statSync(lockPath).mtimeMs > staleMs) {
unlinkSync(lockPath); // presumed orphaned
continue;
}
} catch {
/* lock vanished between failed create and stat — retry immediately */
}
if (Date.now() - start > timeoutMs) throw new Error(`lock timeout: ${lockPath}`);
await sleep(200);
}
}
try {
return await fn();
} finally {
try {
unlinkSync(lockPath);
} catch {
/* already gone */
}
}
}