superlock 1.2.10 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/package.json +1 -1
- package/src/create.js +44 -24
- package/src/index.d.ts +4 -2
- package/src/index.js +3 -2
package/README.md
CHANGED
|
@@ -48,6 +48,26 @@ console.log(executions)
|
|
|
48
48
|
|
|
49
49
|
Just call `withLock(n)` being `n` the maximum of concurrency desired for the lock.
|
|
50
50
|
|
|
51
|
+
### with cancellation
|
|
52
|
+
|
|
53
|
+
Pass an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to drop an acquisition that is still waiting in the queue — handy for shedding work whose caller already gave up (a timed-out request, a disconnected client):
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const { withLock } = require('superlock')
|
|
57
|
+
|
|
58
|
+
const lock = withLock()
|
|
59
|
+
const controller = new AbortController()
|
|
60
|
+
|
|
61
|
+
const pending = lock(() => doWork(), controller.signal)
|
|
62
|
+
|
|
63
|
+
// caller no longer needs the result: give the queued slot back
|
|
64
|
+
controller.abort()
|
|
65
|
+
|
|
66
|
+
await pending // resolves `undefined`; `doWork` never runs
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If the signal aborts **before** the slot is granted, the acquisition is dropped: it never takes a slot and the wrapped function never runs. Aborting **after** the slot is granted is a no-op (the work is already running). Aborting is silent — it resolves instead of throwing — so shedding a lot of queued work never produces rejections to handle.
|
|
70
|
+
|
|
51
71
|
## API
|
|
52
72
|
|
|
53
73
|
### withLock([concurrency=1])
|
|
@@ -66,6 +86,16 @@ await lock(() => {
|
|
|
66
86
|
|
|
67
87
|
The lock will be automatically released after your code execution is done even if an error occurred, avoiding [deadlock](https://en.wikipedia.org/wiki/Deadlock) situations.
|
|
68
88
|
|
|
89
|
+
It also accepts an optional `AbortSignal` as second argument:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
await lock(() => {
|
|
93
|
+
/* your code execution */
|
|
94
|
+
}, signal)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If `signal` aborts while the call is still queued, the function is skipped and the call resolves `undefined` instead of running. Aborting after the slot is granted has no effect.
|
|
98
|
+
|
|
69
99
|
#### concurrency
|
|
70
100
|
|
|
71
101
|
Type: `number`<br>
|
|
@@ -73,6 +103,12 @@ Default: `1`
|
|
|
73
103
|
|
|
74
104
|
It sets the maximum of concurrency allowed for the lock.
|
|
75
105
|
|
|
106
|
+
#### signal
|
|
107
|
+
|
|
108
|
+
Type: `AbortSignal`
|
|
109
|
+
|
|
110
|
+
When provided, aborting it drops the acquisition while it is still waiting in the queue: no slot is taken and the wrapped function never runs.
|
|
111
|
+
|
|
76
112
|
### .isLocked()
|
|
77
113
|
|
|
78
114
|
Type: `boolean`
|
package/package.json
CHANGED
package/src/create.js
CHANGED
|
@@ -7,33 +7,37 @@ class Node {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
class LinkedList {
|
|
10
|
+
constructor () {
|
|
11
|
+
this.length = 0
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
enqueue (data) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
current.next = new Node(data)
|
|
19
|
-
}
|
|
15
|
+
const node = new Node(data)
|
|
16
|
+
node.prev = this.tail
|
|
17
|
+
if (this.tail) this.tail.next = node
|
|
18
|
+
else this.head = node
|
|
19
|
+
this.tail = node
|
|
20
|
+
this.length++
|
|
21
|
+
return node
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
dequeue () {
|
|
23
25
|
if (!this.head) return
|
|
24
|
-
const data = this.head
|
|
25
|
-
this.
|
|
26
|
+
const { data } = this.head
|
|
27
|
+
this.remove(this.head)
|
|
26
28
|
return data
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
remove (node) {
|
|
32
|
+
if (node.prev) node.prev.next = node.next
|
|
33
|
+
else this.head = node.next
|
|
34
|
+
if (node.next) node.next.prev = node.prev
|
|
35
|
+
else this.tail = node.prev
|
|
36
|
+
this.length--
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
size () {
|
|
30
|
-
|
|
31
|
-
let current = this.head
|
|
32
|
-
while (current) {
|
|
33
|
-
count++
|
|
34
|
-
current = current.next
|
|
35
|
-
}
|
|
36
|
-
return count
|
|
40
|
+
return this.length
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -42,8 +46,8 @@ module.exports = (slots = 1) => {
|
|
|
42
46
|
|
|
43
47
|
const release = () => {
|
|
44
48
|
++slots
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
49
|
+
const waiter = queue.dequeue()
|
|
50
|
+
if (waiter) return waiter.acquire()
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const acquire = resolve => {
|
|
@@ -51,10 +55,26 @@ module.exports = (slots = 1) => {
|
|
|
51
55
|
resolve(release)
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
const lock =
|
|
55
|
-
new Promise(resolve =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const lock = signal =>
|
|
59
|
+
new Promise(resolve => {
|
|
60
|
+
if (signal?.aborted) return resolve(null)
|
|
61
|
+
if (!lock.isLocked()) return acquire(resolve)
|
|
62
|
+
|
|
63
|
+
const waiter = { acquire: () => acquire(resolve) }
|
|
64
|
+
const node = queue.enqueue(waiter)
|
|
65
|
+
|
|
66
|
+
if (signal !== undefined) {
|
|
67
|
+
const onAbort = () => {
|
|
68
|
+
queue.remove(node)
|
|
69
|
+
resolve(null)
|
|
70
|
+
}
|
|
71
|
+
waiter.acquire = () => {
|
|
72
|
+
signal.removeEventListener('abort', onAbort)
|
|
73
|
+
acquire(resolve)
|
|
74
|
+
}
|
|
75
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
76
|
+
}
|
|
77
|
+
})
|
|
58
78
|
|
|
59
79
|
lock.isLocked = () => slots === 0
|
|
60
80
|
|
package/src/index.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
declare type Release = () => void;
|
|
2
2
|
declare type Acquire = (resolve: (release: Release) => void) => void;
|
|
3
|
-
declare type Lock = () => Promise<Release>;
|
|
3
|
+
declare type Lock = (signal?: AbortSignal) => Promise<Release | null>;
|
|
4
4
|
|
|
5
5
|
interface LockInterface extends Lock {
|
|
6
6
|
isLocked: () => boolean;
|
|
7
|
+
awaiting: () => number;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
declare function createLock(slots?: number): LockInterface;
|
|
10
11
|
|
|
11
|
-
declare type WithLock = <T>(fn: () => Promise<T
|
|
12
|
+
declare type WithLock = <T>(fn: () => Promise<T>, signal?: AbortSignal) => Promise<T | undefined>;
|
|
12
13
|
|
|
13
14
|
interface WithLockInterface extends WithLock {
|
|
14
15
|
isLocked: () => boolean;
|
|
16
|
+
awaiting: () => number;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
declare function withLock(opts?: number): WithLockInterface;
|
package/src/index.js
CHANGED
|
@@ -5,8 +5,9 @@ const createLock = require('./create')
|
|
|
5
5
|
const withLock = opts => {
|
|
6
6
|
const lock = createLock(opts)
|
|
7
7
|
|
|
8
|
-
const withLock = async fn => {
|
|
9
|
-
const release = await lock()
|
|
8
|
+
const withLock = async (fn, signal) => {
|
|
9
|
+
const release = await lock(signal)
|
|
10
|
+
if (!release) return
|
|
10
11
|
try {
|
|
11
12
|
return await fn()
|
|
12
13
|
} finally {
|