79 lines
2.0 KiB
JavaScript
79 lines
2.0 KiB
JavaScript
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
|
|
function delay(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function acquireFileLock(lockDir, options = {}) {
|
|
const timeoutMs = Number(options.timeoutMs || 10 * 60 * 1000);
|
|
const pollMs = Number(options.pollMs || 1000);
|
|
const deadline = Date.now() + timeoutMs;
|
|
const parent = path.dirname(lockDir);
|
|
await fs.mkdir(parent, { recursive: true });
|
|
|
|
while (Date.now() <= deadline) {
|
|
try {
|
|
await fs.mkdir(lockDir);
|
|
await fs.writeFile(path.join(lockDir, 'owner.json'), JSON.stringify({
|
|
pid: process.pid,
|
|
acquiredAt: new Date().toISOString(),
|
|
}, null, 2), 'utf8');
|
|
return;
|
|
} catch (error) {
|
|
if (error && error.code !== 'EEXIST') throw error;
|
|
if (await removeOrphanedLock(lockDir)) continue;
|
|
await delay(pollMs);
|
|
}
|
|
}
|
|
|
|
throw new Error(`Timed out waiting for ERP task lock: ${lockDir}`);
|
|
}
|
|
|
|
async function readLockOwner(lockDir) {
|
|
try {
|
|
const text = await fs.readFile(path.join(lockDir, 'owner.json'), 'utf8');
|
|
const owner = JSON.parse(text);
|
|
const pid = Number(owner && owner.pid);
|
|
return Number.isInteger(pid) && pid > 0 ? { ...owner, pid } : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function processIsRunning(pid) {
|
|
try {
|
|
process.kill(pid, 0);
|
|
return true;
|
|
} catch (error) {
|
|
if (error && error.code === 'ESRCH') return false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
async function removeOrphanedLock(lockDir) {
|
|
const owner = await readLockOwner(lockDir);
|
|
if (!owner || processIsRunning(owner.pid)) return false;
|
|
await fs.rm(lockDir, { recursive: true, force: true });
|
|
return true;
|
|
}
|
|
|
|
async function releaseFileLock(lockDir) {
|
|
await fs.rm(lockDir, { recursive: true, force: true });
|
|
}
|
|
|
|
async function withFileLock(lockDir, fn, options = {}) {
|
|
await acquireFileLock(lockDir, options);
|
|
try {
|
|
return await fn();
|
|
} finally {
|
|
await releaseFileLock(lockDir);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
acquireFileLock,
|
|
releaseFileLock,
|
|
withFileLock,
|
|
};
|