Skip to content

Commit

Permalink
feat: add process lock for optional use in non-browser environments (…
Browse files Browse the repository at this point in the history
…React Native)
  • Loading branch information
hf committed Oct 29, 2024
1 parent cb052a9 commit d41d2cd
Showing 1 changed file with 61 additions and 0 deletions.
61 changes: 61 additions & 0 deletions src/lib/locks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export abstract class LockAcquireTimeoutError extends Error {
}

export class NavigatorLockAcquireTimeoutError extends LockAcquireTimeoutError {}
export class ProcessLockAcquireTimeoutError extends LockAcquireTimeoutError {}

/**
* Implements a global exclusive lock using the Navigator LockManager API. It
Expand Down Expand Up @@ -141,3 +142,63 @@ export async function navigatorLock<R>(
}
)
}

const PROCESS_LOCKS: { [name: string]: Promise<any> } = {}

/**
* Implements a global exclusive lock that works only in the current process.
* Useful for environments like React Native or other non-browser
* single-process (i.e. no concept of "tabs") environments.
*
* Use {@link #navigatorLock} in browser environments.
*
* @param name Name of the lock to be acquired.
* @param acquireTimeout If negative, no timeout. If 0 an error is thrown if
* the lock can't be acquired without waiting. If positive, the lock acquire
* will time out after so many milliseconds. An error is
* a timeout if it has `isAcquireTimeout` set to true.
* @param fn The operation to run once the lock is acquired.
*/
export async function processLock<R>(
name: string,
acquireTimeout: number,
fn: () => Promise<R>
): Promise<R> {
const currentOperation = Promise.race(
[
(PROCESS_LOCKS[name] ?? Promise.resolve()).catch((e: any) => {

Check warning on line 169 in src/lib/locks.ts

View workflow job for this annotation

GitHub Actions / Test / OS ubuntu-latest / Node 18

'e' is defined but never used

Check warning on line 169 in src/lib/locks.ts

View workflow job for this annotation

GitHub Actions / Test / OS ubuntu-latest / Node 20

'e' is defined but never used
// ignore error of previous operation that we're waiting to finish
return null
}),
acquireTimeout >= 0
? new Promise((_, reject) => {
setTimeout(() => {
reject(
new ProcessLockAcquireTimeoutError(
`Acquring process lock with name "${name}" timed out`
)
)
}, acquireTimeout)
})
: null,
].filter((x) => x)
)
.catch((e: any) => {
if (e && e.isAcquireTimeout) {
throw e
}

return null
})
.then(async () => {
// previous operations finished and we didn't get a race on the acquire
// timeout, so the current operation can finally start
return await fn()
})

PROCESS_LOCKS[name] = currentOperation

// finally wait for the current operation to finish successfully, with an
// error or with an acquire timeout error
return await currentOperation
}

0 comments on commit d41d2cd

Please sign in to comment.