@sema-agent/core
Version:
Stateless, task-oriented AI agent core
99 lines (98 loc) • 3.26 kB
JavaScript
import { mkdirSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { randomBytes } from "node:crypto";
const DEFAULT_STALE_MS = 10_000;
const RETRIES = 10;
const MIN_TIMEOUT_MS = 5;
const MAX_TIMEOUT_MS = 100;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function acquireCcLock(target, opts) {
const lockPath = `${target}.lock`;
const ownerPath = join(lockPath, "owner");
const staleMs = opts?.staleMs ?? DEFAULT_STALE_MS;
const nonce = randomBytes(12).toString("hex");
const ownedByUs = () => {
try {
return readFileSync(ownerPath, "utf8") === nonce;
}
catch {
return false;
}
};
for (let attempt = 0;; attempt++) {
if (attempt >= RETRIES) {
throw new Error(`cc lockfile: could not acquire ${lockPath} after ${RETRIES} retries`);
}
try {
mkdirSync(lockPath);
writeFileSync(ownerPath, nonce);
let compromised = false;
const beat = setInterval(() => {
if (!ownedByUs()) {
compromised = true;
clearInterval(beat);
return;
}
const now = new Date();
try {
utimesSync(lockPath, now, now);
}
catch {
compromised = true;
clearInterval(beat);
}
}, Math.max(1, Math.floor(staleMs / 2)));
beat.unref?.();
return () => {
clearInterval(beat);
if (compromised || !ownedByUs()) {
throw new Error(`cc lockfile: lock on ${target} was reclaimed while held (critical section exceeded the ` +
`stale window and another process took over) — the guarded write may have raced; do not trust it`);
}
try {
rmSync(lockPath, { recursive: true, force: true });
}
catch {
}
};
}
catch (err) {
if (err.code !== "EEXIST")
throw err;
try {
const st = statSync(lockPath);
if (Date.now() - st.mtimeMs > staleMs) {
const st2 = statSync(lockPath);
if (st2.mtimeMs === st.mtimeMs) {
try {
rmSync(lockPath, { recursive: true, force: true });
}
catch {
}
}
}
}
catch {
}
const backoff = Math.min(MAX_TIMEOUT_MS, MIN_TIMEOUT_MS * 2 ** attempt);
await sleep(backoff / 2 + Math.random() * (backoff / 2));
}
}
}
export async function withCcLock(target, fn) {
const release = await acquireCcLock(target);
let result;
try {
result = await fn();
}
catch (err) {
try {
release();
}
catch {
}
throw err;
}
release();
return result;
}