(params: {
lockDirPath: string;
owner: ProcessLockOwner;
timeoutMs?: number;
pollMs?: number;
ownerGraceMs?: number;
description?: string;
})
| 15 | }; |
| 16 | |
| 17 | export async function acquireProcessLock(params: { |
| 18 | lockDirPath: string; |
| 19 | owner: ProcessLockOwner; |
| 20 | timeoutMs?: number; |
| 21 | pollMs?: number; |
| 22 | ownerGraceMs?: number; |
| 23 | description?: string; |
| 24 | }): Promise<() => Promise<void>> { |
| 25 | const { lockDirPath, owner } = params; |
| 26 | const ownerFilePath = path.join(lockDirPath, 'owner.json'); |
| 27 | const deadline = Date.now() + (params.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS); |
| 28 | const pollMs = params.pollMs ?? DEFAULT_LOCK_POLL_MS; |
| 29 | const ownerGraceMs = params.ownerGraceMs ?? DEFAULT_LOCK_OWNER_GRACE_MS; |
| 30 | const description = params.description ?? 'process lock'; |
| 31 | |
| 32 | fs.mkdirSync(path.dirname(lockDirPath), { recursive: true }); |
| 33 | |
| 34 | while (Date.now() < deadline) { |
| 35 | try { |
| 36 | fs.mkdirSync(lockDirPath); |
| 37 | writeProcessLockOwner(ownerFilePath, owner); |
| 38 | let released = false; |
| 39 | return async () => { |
| 40 | if (released) return; |
| 41 | released = true; |
| 42 | fs.rmSync(lockDirPath, { recursive: true, force: true }); |
| 43 | }; |
| 44 | } catch (error) { |
| 45 | const err = error as NodeJS.ErrnoException; |
| 46 | if (err.code !== 'EEXIST') { |
| 47 | throw err; |
| 48 | } |
| 49 | if (clearStaleProcessLock(lockDirPath, ownerFilePath, ownerGraceMs)) { |
| 50 | continue; |
| 51 | } |
| 52 | await sleep(pollMs); |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | throw new AppError('COMMAND_FAILED', `Timed out waiting for ${description}`, { |
| 57 | lockDirPath, |
| 58 | ...readProcessLockDiagnostics(lockDirPath, ownerFilePath), |
| 59 | }); |
| 60 | } |
| 61 | |
| 62 | function writeProcessLockOwner(ownerFilePath: string, owner: ProcessLockOwner): void { |
| 63 | const tmpOwnerFilePath = `${ownerFilePath}.${process.pid}.${Date.now()}.tmp`; |
no test coverage detected