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 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.2",
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
- entry = { key, queue: new LinkedList(), ringNode: ring.enqueue(key) }
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
- const grant = () => {
33
- if (ring.size() === 0) return
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 node =
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--
@@ -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