69 lines
2.0 KiB
JavaScript
69 lines
2.0 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs/promises');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
|
|
const lock = require('../erp_task_lock');
|
|
|
|
function delay(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
test('withFileLock serializes concurrent tasks', async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'erp-lock-'));
|
|
const lockDir = path.join(root, 'erp-task.lock');
|
|
const events = [];
|
|
|
|
const first = lock.withFileLock(lockDir, async () => {
|
|
events.push('a-start');
|
|
await delay(20);
|
|
events.push('a-end');
|
|
}, { pollMs: 1, timeoutMs: 1000 });
|
|
|
|
await delay(2);
|
|
|
|
const second = lock.withFileLock(lockDir, async () => {
|
|
events.push('b-start');
|
|
events.push('b-end');
|
|
}, { pollMs: 1, timeoutMs: 1000 });
|
|
|
|
await Promise.all([first, second]);
|
|
|
|
assert.deepEqual(events, ['a-start', 'a-end', 'b-start', 'b-end']);
|
|
});
|
|
|
|
test('withFileLock removes the lock after a failed task', async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'erp-lock-'));
|
|
const lockDir = path.join(root, 'erp-task.lock');
|
|
|
|
await assert.rejects(
|
|
() => lock.withFileLock(lockDir, async () => {
|
|
throw new Error('boom');
|
|
}, { pollMs: 1, timeoutMs: 1000 }),
|
|
/boom/
|
|
);
|
|
|
|
await lock.withFileLock(lockDir, async () => 'ok', { pollMs: 1, timeoutMs: 1000 });
|
|
});
|
|
|
|
test('withFileLock recovers an orphaned lock whose owner process no longer exists', async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'erp-lock-'));
|
|
const lockDir = path.join(root, 'erp-task.lock');
|
|
|
|
await fs.mkdir(lockDir);
|
|
await fs.writeFile(
|
|
path.join(lockDir, 'owner.json'),
|
|
JSON.stringify({ pid: 99999999, acquiredAt: '2026-06-26T16:18:21.576Z' }),
|
|
'utf8'
|
|
);
|
|
|
|
let ran = false;
|
|
await lock.withFileLock(lockDir, async () => {
|
|
ran = true;
|
|
}, { pollMs: 1, timeoutMs: 1000 });
|
|
|
|
assert.equal(ran, true);
|
|
await assert.rejects(() => fs.stat(lockDir), /ENOENT/);
|
|
});
|