Skip to content

Commit 773e3bf

Browse files
committed
feat: add a tryAcquire method
1 parent c3cfae2 commit 773e3bf

4 files changed

Lines changed: 62 additions & 2 deletions

File tree

docs/content/docs/api.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@ await lock.run({ retry: { timeout: '1000ms' } }, async () => {
5050

5151
Accept an optional object with the same properties as `acquire`.
5252

53+
### `tryAcquire`
54+
55+
Try to acquire the lock immediately. If the lock is already acquired, it will throws a `E_LOCK_ALREADY_ACQUIRED` error.
56+
57+
```ts
58+
import { errors } from '@verrou/core'
59+
60+
const lock = verrou.createLock('key')
61+
try {
62+
await lock.tryAcquire()
63+
} catch (err) {
64+
if (err instanceof E_LOCK_ALREADY_ACQUIRED) {
65+
66+
}
67+
}
68+
```
69+
5370
### `isLocked`
5471

5572
Check if the lock is acquired.

packages/verrou/src/errors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,11 @@ export const E_LOCK_STORAGE_ERROR = createError<[{ message: string }]>(
2323
'Lock storage error: %s',
2424
'E_LOCK_STORAGE_ERROR',
2525
)
26+
27+
/**
28+
* Thrown when the lock is already acquired by someone else
29+
*/
30+
export const E_LOCK_ALREADY_ACQUIRED = createError(
31+
'Lock is already acquired',
32+
'E_LOCK_ALREDY_ACQUIRED',
33+
)

packages/verrou/src/lock.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { setTimeout } from 'node:timers/promises'
22
import { InvalidArgumentsException } from '@poppinss/utils'
33

4-
import { E_LOCK_TIMEOUT } from './errors.js'
54
import { resolveDuration } from './helpers.js'
5+
import { E_LOCK_ALREADY_ACQUIRED, E_LOCK_TIMEOUT } from './errors.js'
66
import type {
77
Duration,
88
LockAcquireOptions,
@@ -105,6 +105,15 @@ export class Lock {
105105
this.#config.logger.debug({ key: this.#key }, 'Lock acquired')
106106
}
107107

108+
/**
109+
* Try to acquire the lock immediately or throw an error
110+
*/
111+
async tryAcquire() {
112+
const result = await this.#lockStore.save(this.#key, this.#owner, this.#ttl)
113+
if (!result) throw new E_LOCK_ALREADY_ACQUIRED()
114+
this.#expirationTime = this.#ttl ? Date.now() + this.#ttl : null
115+
}
116+
108117
/**
109118
* Acquire the lock, run the callback and release the lock automatically
110119
* after the callback is done.

packages/verrou/tests/lock.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { noopLogger } from 'typescript-log'
33
import { setTimeout } from 'node:timers/promises'
44

55
import { Lock } from '../src/lock.js'
6-
import { E_LOCK_TIMEOUT } from '../src/errors.js'
76
import { MemoryStore } from '../src/drivers/memory.js'
87
import { NullStore } from '../test_helpers/null_store.js'
8+
import { E_LOCK_ALREADY_ACQUIRED, E_LOCK_TIMEOUT } from '../src/errors.js'
99

1010
const defaultOptions = {
1111
retry: {
@@ -334,4 +334,30 @@ test.group('Lock', () => {
334334
const elapsed = Date.now() - start
335335
assert.isAbove(elapsed, 500)
336336
})
337+
338+
test('tryAcquire works', async ({ assert }) => {
339+
const store = new MemoryStore()
340+
const lock = new Lock('foo', store, defaultOptions, undefined, 1000)
341+
342+
assert.deepEqual(await lock.isLocked(), false)
343+
344+
await lock.tryAcquire()
345+
346+
assert.deepEqual(await lock.isLocked(), true)
347+
assert.deepEqual(lock.getRemainingTime(), 1000)
348+
assert.deepEqual(lock.isExpired(), false)
349+
})
350+
351+
test('tryAcquires throws timeout error when lock is not available', async ({ assert }) => {
352+
class FakeStore extends NullStore {
353+
async save(_key: string) {
354+
return false
355+
}
356+
}
357+
358+
const lock = new Lock('foo', new FakeStore(), defaultOptions)
359+
360+
// @ts-ignore
361+
await assert.rejects(() => lock.tryAcquire(), E_LOCK_ALREADY_ACQUIRED.message)
362+
})
337363
})

0 commit comments

Comments
 (0)