superlock 1.4.2 → 1.4.3
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/package.json +1 -1
- package/src/fair.js +38 -4
- package/src/linked-list.js +10 -0
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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.4.
|
|
5
|
+
"version": "1.4.3",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"exports": {
|
package/src/fair.js
CHANGED
|
@@ -5,11 +5,16 @@ const LinkedList = require('./linked-list')
|
|
|
5
5
|
const SHARED = Symbol('shared')
|
|
6
6
|
const NONE = Symbol('none')
|
|
7
7
|
|
|
8
|
+
// Map keys use SameValueZero, so NaN is one key and === cannot find it.
|
|
9
|
+
const sameKey = (a, b) => a === b || (Number.isNaN(a) && Number.isNaN(b))
|
|
10
|
+
|
|
8
11
|
module.exports = (slots = 1) => {
|
|
9
12
|
const queues = new Map()
|
|
10
13
|
const ring = new LinkedList()
|
|
11
14
|
let waiting = 0
|
|
12
15
|
let last = NONE
|
|
16
|
+
let seq = 0
|
|
17
|
+
let barrier = 0
|
|
13
18
|
|
|
14
19
|
const drop = entry => {
|
|
15
20
|
const predecessor = entry.ringNode.prev
|
|
@@ -21,19 +26,48 @@ module.exports = (slots = 1) => {
|
|
|
21
26
|
const enqueue = (key, waiter) => {
|
|
22
27
|
let entry = queues.get(key)
|
|
23
28
|
if (!entry) {
|
|
24
|
-
|
|
29
|
+
// The holder is the cursor and is not on the ring yet. Put that key
|
|
30
|
+
// at the head so the next grant starts on the keys already waiting
|
|
31
|
+
// and reaches the holder last. At the tail, the cursor would step
|
|
32
|
+
// into keys that arrive later and skip the ones already in front.
|
|
33
|
+
const ringNode = sameKey(key, last)
|
|
34
|
+
? ring.prepend(key)
|
|
35
|
+
: ring.enqueue(key)
|
|
36
|
+
entry = { key, queue: new LinkedList(), ringNode }
|
|
25
37
|
queues.set(key, entry)
|
|
26
38
|
}
|
|
27
39
|
waiting++
|
|
28
40
|
waiter.entry = entry
|
|
41
|
+
waiter.joinSeq = ++seq
|
|
29
42
|
waiter.node = entry.queue.enqueue(waiter)
|
|
30
43
|
}
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
// The key's oldest waiter decides. A later waiter on a key that joined
|
|
46
|
+
// this round has a newer sequence and must not inherit the round.
|
|
47
|
+
const eligible = entry => entry.queue.head.data.joinSeq <= barrier
|
|
48
|
+
|
|
49
|
+
// Start just after the cursor and wrap once. Stop is required: the
|
|
50
|
+
// ring is not circular, so the walk jumps from the tail back to the head.
|
|
51
|
+
const selectNode = () => {
|
|
34
52
|
const current = queues.get(last)
|
|
35
|
-
const
|
|
53
|
+
const start =
|
|
36
54
|
current && current.ringNode.next ? current.ringNode.next : ring.head
|
|
55
|
+
let node = start
|
|
56
|
+
do {
|
|
57
|
+
if (eligible(queues.get(node.data))) return node
|
|
58
|
+
node = node.next || ring.head
|
|
59
|
+
} while (node !== start)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const grant = () => {
|
|
63
|
+
if (ring.size() === 0) return
|
|
64
|
+
// Waiters past the barrier stay out until every earlier waiter has
|
|
65
|
+
// been served. When none of those remain, move the barrier up.
|
|
66
|
+
let node = selectNode()
|
|
67
|
+
if (!node) {
|
|
68
|
+
barrier = seq
|
|
69
|
+
node = selectNode()
|
|
70
|
+
}
|
|
37
71
|
const entry = queues.get(node.data)
|
|
38
72
|
const waiter = entry.queue.dequeue()
|
|
39
73
|
waiting--
|
package/src/linked-list.js
CHANGED
|
@@ -21,6 +21,16 @@ module.exports = class LinkedList {
|
|
|
21
21
|
return node
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
prepend (data) {
|
|
25
|
+
const node = new Node(data)
|
|
26
|
+
node.next = this.head
|
|
27
|
+
if (this.head) this.head.prev = node
|
|
28
|
+
else this.tail = node
|
|
29
|
+
this.head = node
|
|
30
|
+
this.length++
|
|
31
|
+
return node
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
dequeue () {
|
|
25
35
|
if (!this.head) return
|
|
26
36
|
const { data } = this.head
|