disclaw/src/runtime/lockfile.ts
Nick Tabeling dc4a1ef241 feat(runtime): add startup lockfile to prevent double-start
Implements acquireLock() in src/runtime/lockfile.ts that writes a PID-stamped
lock file under workspaces_root, detects live vs stale locks via process.kill(pid, 0),
and returns a release function. src/index.ts acquires the lock before DB init and
releases it in the graceful-shutdown handler. Four unit tests cover acquire, duplicate-
acquire rejection, stale-lock recovery, and release.
2026-04-09 11:49:17 +02:00

57 lines
1.2 KiB
TypeScript

import * as fs from "fs";
import * as path from "path";
interface LockData {
pid: number;
started: string;
}
function isProcessRunning(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
export async function acquireLock(lockPath: string): Promise<() => void> {
if (fs.existsSync(lockPath)) {
const raw = fs.readFileSync(lockPath, "utf-8");
let data: LockData;
try {
data = JSON.parse(raw) as LockData;
} catch {
// Corrupted lock file — treat as stale, overwrite
data = { pid: 0, started: "" };
}
if (data.pid && isProcessRunning(data.pid)) {
throw new Error(
`DisClaw bereits aktiv (PID ${data.pid}). Lockfile: ${lockPath}`
);
}
// Stale lock — fall through and overwrite
}
// Ensure parent directory exists
const dir = path.dirname(lockPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const lockData: LockData = {
pid: process.pid,
started: new Date().toISOString(),
};
fs.writeFileSync(lockPath, JSON.stringify(lockData), "utf-8");
const release = (): void => {
if (fs.existsSync(lockPath)) {
fs.unlinkSync(lockPath);
}
};
return release;
}