superlock 1.3.5 → 1.4.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 +3 -3
- package/package.json +3 -2
- package/src/create.js +1 -40
- package/src/fair.js +86 -0
- package/src/index.d.ts +10 -1
- package/src/index.js +2 -1
- package/src/linked-list.js +42 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
](https://github.com/Kikobeats/superlock/releases)
|
|
2
|
+
[](https://coveralls.io/github/Kikobeats/superlock)
|
|
3
|
+
[](https://www.npmjs.com/package/superlock)
|
|
4
4
|
|
|
5
5
|
> A mutex/semaphore implementation made easy to use.
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
"name": "superlock",
|
|
3
3
|
"description": "Minimal async mutex and semaphore for serializing critical sections in Node.js.",
|
|
4
4
|
"homepage": "https://github.com/Kikobeats/superlock",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.4.1",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.js",
|
|
10
|
-
"./create": "./src/create.js"
|
|
10
|
+
"./create": "./src/create.js",
|
|
11
|
+
"./fair": "./src/fair.js"
|
|
11
12
|
},
|
|
12
13
|
"author": {
|
|
13
14
|
"email": "josefrancisco.verdu@gmail.com",
|
package/src/create.js
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
constructor (data) {
|
|
5
|
-
this.data = data
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class LinkedList {
|
|
10
|
-
constructor () {
|
|
11
|
-
this.length = 0
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
enqueue (data) {
|
|
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
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
dequeue () {
|
|
25
|
-
if (!this.head) return
|
|
26
|
-
const { data } = this.head
|
|
27
|
-
this.remove(this.head)
|
|
28
|
-
return data
|
|
29
|
-
}
|
|
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
|
-
|
|
39
|
-
size () {
|
|
40
|
-
return this.length
|
|
41
|
-
}
|
|
42
|
-
}
|
|
3
|
+
const LinkedList = require('./linked-list')
|
|
43
4
|
|
|
44
5
|
module.exports = (slots = 1) => {
|
|
45
6
|
const queue = new LinkedList()
|
package/src/fair.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LinkedList = require('./linked-list')
|
|
4
|
+
|
|
5
|
+
const SHARED = Symbol('shared')
|
|
6
|
+
const NONE = Symbol('none')
|
|
7
|
+
|
|
8
|
+
module.exports = (slots = 1) => {
|
|
9
|
+
const queues = new Map()
|
|
10
|
+
const ring = new LinkedList()
|
|
11
|
+
let waiting = 0
|
|
12
|
+
let last = NONE
|
|
13
|
+
|
|
14
|
+
const drop = entry => {
|
|
15
|
+
ring.remove(entry.ringNode)
|
|
16
|
+
queues.delete(entry.key)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const enqueue = (key, waiter) => {
|
|
20
|
+
let entry = queues.get(key)
|
|
21
|
+
if (!entry) {
|
|
22
|
+
entry = { key, queue: new LinkedList(), ringNode: ring.enqueue(key) }
|
|
23
|
+
queues.set(key, entry)
|
|
24
|
+
}
|
|
25
|
+
waiting++
|
|
26
|
+
waiter.entry = entry
|
|
27
|
+
waiter.node = entry.queue.enqueue(waiter)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const grant = () => {
|
|
31
|
+
if (ring.size() === 0) return
|
|
32
|
+
const current = queues.get(last)
|
|
33
|
+
const node =
|
|
34
|
+
current && current.ringNode.next ? current.ringNode.next : ring.head
|
|
35
|
+
const entry = queues.get(node.data)
|
|
36
|
+
const waiter = entry.queue.dequeue()
|
|
37
|
+
waiting--
|
|
38
|
+
if (entry.queue.size() === 0) drop(entry)
|
|
39
|
+
last = entry.key
|
|
40
|
+
waiter.acquire()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const release = () => {
|
|
44
|
+
++slots
|
|
45
|
+
grant()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const acquire = (resolve, key) => {
|
|
49
|
+
--slots
|
|
50
|
+
last = key
|
|
51
|
+
resolve(release)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const lock = (signal, key) =>
|
|
55
|
+
new Promise(resolve => {
|
|
56
|
+
if (signal != null && typeof signal.addEventListener !== 'function') {
|
|
57
|
+
throw new TypeError('`signal` needs to be an AbortSignal.')
|
|
58
|
+
}
|
|
59
|
+
const id = key === undefined ? SHARED : key
|
|
60
|
+
if (signal?.aborted) return resolve(null)
|
|
61
|
+
if (!lock.isLocked()) return acquire(resolve, id)
|
|
62
|
+
|
|
63
|
+
const waiter = { acquire: () => acquire(resolve, id) }
|
|
64
|
+
enqueue(id, waiter)
|
|
65
|
+
|
|
66
|
+
if (signal != null) {
|
|
67
|
+
const onAbort = () => {
|
|
68
|
+
waiter.entry.queue.remove(waiter.node)
|
|
69
|
+
waiting--
|
|
70
|
+
if (waiter.entry.queue.size() === 0) drop(waiter.entry)
|
|
71
|
+
resolve(null)
|
|
72
|
+
}
|
|
73
|
+
waiter.acquire = () => {
|
|
74
|
+
signal.removeEventListener('abort', onAbort)
|
|
75
|
+
acquire(resolve, id)
|
|
76
|
+
}
|
|
77
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
lock.isLocked = () => slots === 0
|
|
82
|
+
|
|
83
|
+
lock.awaiting = () => waiting
|
|
84
|
+
|
|
85
|
+
return lock
|
|
86
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ interface LockInterface extends Lock {
|
|
|
9
9
|
|
|
10
10
|
declare function createLock(slots?: number): LockInterface;
|
|
11
11
|
|
|
12
|
+
declare type FairLock = (signal: AbortSignal | undefined | null, key?: unknown) => Promise<Release | null>;
|
|
13
|
+
|
|
14
|
+
interface FairLockInterface extends FairLock {
|
|
15
|
+
isLocked: () => boolean;
|
|
16
|
+
awaiting: () => number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare function createFairLock(slots?: number): FairLockInterface;
|
|
20
|
+
|
|
12
21
|
declare type WithLock = <T>(fn: () => Promise<T>, signal?: AbortSignal) => Promise<T | undefined>;
|
|
13
22
|
|
|
14
23
|
interface WithLockInterface extends WithLock {
|
|
@@ -18,4 +27,4 @@ interface WithLockInterface extends WithLock {
|
|
|
18
27
|
|
|
19
28
|
declare function withLock(opts?: number): WithLockInterface;
|
|
20
29
|
|
|
21
|
-
export { withLock, createLock, WithLockInterface };
|
|
30
|
+
export { withLock, createLock, createFairLock, WithLockInterface, FairLockInterface };
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const createFairLock = require('./fair')
|
|
3
4
|
const createLock = require('./create')
|
|
4
5
|
|
|
5
6
|
const withLock = opts => {
|
|
@@ -21,4 +22,4 @@ const withLock = opts => {
|
|
|
21
22
|
return withLock
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
module.exports = { withLock, createLock }
|
|
25
|
+
module.exports = { withLock, createLock, createFairLock }
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class Node {
|
|
4
|
+
constructor (data) {
|
|
5
|
+
this.data = data
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = class LinkedList {
|
|
10
|
+
constructor () {
|
|
11
|
+
this.length = 0
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
enqueue (data) {
|
|
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
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
dequeue () {
|
|
25
|
+
if (!this.head) return
|
|
26
|
+
const { data } = this.head
|
|
27
|
+
this.remove(this.head)
|
|
28
|
+
return data
|
|
29
|
+
}
|
|
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
|
+
|
|
39
|
+
size () {
|
|
40
|
+
return this.length
|
|
41
|
+
}
|
|
42
|
+
}
|