Files
2026-07-13 19:57:46 +08:00

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,
};