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 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
@@ -2,7 +2,7 @@
2
2
  "name": "superlock",
3
3
  "description": "The most simple mutex/semaphore implementation",
4
4
  "homepage": "https://github.com/Kikobeats/superlock",
5
- "version": "1.2.10",
5
+ "version": "1.3.1",
6
6
  "types": "src/index.d.ts",
7
7
  "main": "src/index.js",
8
8
  "exports": {
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
- if (!this.head) {
12
- this.head = new Node(data)
13
- } else {
14
- let current = this.head
15
- while (current.next) {
16
- current = current.next
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.data
25
- this.head = this.head.next
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
- let count = 0
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 fn = queue.dequeue()
46
- if (fn !== undefined) fn()
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
- lock.isLocked() ? queue.enqueue(() => acquire(resolve)) : acquire(resolve)
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>) => 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 {