61 lines
1.6 KiB
JavaScript
61 lines
1.6 KiB
JavaScript
|
|
// Simple PID-based lock file. Atomic via O_EXCL.
|
||
|
|
// If a stale lock is found (PID no longer alive), it is removed.
|
||
|
|
|
||
|
|
import { closeSync, openSync, readFileSync, unlinkSync, writeSync } from 'node:fs';
|
||
|
|
|
||
|
|
function isAlive(pid) {
|
||
|
|
try {
|
||
|
|
process.kill(pid, 0);
|
||
|
|
return true;
|
||
|
|
} catch (e) {
|
||
|
|
return e.code === 'EPERM';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function acquireLock(lockPath) {
|
||
|
|
for (let attempt = 0; attempt < 2; attempt++) {
|
||
|
|
try {
|
||
|
|
const fd = openSync(lockPath, 'wx');
|
||
|
|
writeSync(fd, String(process.pid));
|
||
|
|
closeSync(fd);
|
||
|
|
const release = () => {
|
||
|
|
try {
|
||
|
|
unlinkSync(lockPath);
|
||
|
|
} catch {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
};
|
||
|
|
process.on('exit', release);
|
||
|
|
process.on('SIGINT', () => {
|
||
|
|
release();
|
||
|
|
process.exit(130);
|
||
|
|
});
|
||
|
|
process.on('SIGTERM', () => {
|
||
|
|
release();
|
||
|
|
process.exit(143);
|
||
|
|
});
|
||
|
|
return release;
|
||
|
|
} catch (err) {
|
||
|
|
if (err.code !== 'EEXIST') throw err;
|
||
|
|
// Lock exists — check if owner is still alive
|
||
|
|
let pid = 0;
|
||
|
|
try {
|
||
|
|
pid = parseInt(readFileSync(lockPath, 'utf8').trim(), 10);
|
||
|
|
} catch {
|
||
|
|
/* unreadable */
|
||
|
|
}
|
||
|
|
if (pid && isAlive(pid)) {
|
||
|
|
throw new Error(`Cron already running (PID ${pid}, lock ${lockPath})`);
|
||
|
|
}
|
||
|
|
// Stale lock — remove and retry
|
||
|
|
console.log(`Removing stale lock (PID ${pid || '?'} no longer alive): ${lockPath}`);
|
||
|
|
try {
|
||
|
|
unlinkSync(lockPath);
|
||
|
|
} catch {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
throw new Error(`Could not acquire lock after retries: ${lockPath}`);
|
||
|
|
}
|