Skip to content

Commit 34a948f

Browse files
committed
lib: document nextTick queue internals
Make this code (a bit more) comprehensible by adding some internals docs. With diagrams and everything! πŸŽ‰ PR-URL: #19469 Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Weijia Wang <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Gus Caplan <[email protected]>
1 parent 2d94f77 commit 34a948f

File tree

1 file changed

+73
-9
lines changed

1 file changed

+73
-9
lines changed

β€Žlib/internal/process/next_tick.jsβ€Ž

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,60 @@ function setupNextTick() {
3131
const kHasScheduled = 0;
3232
const kHasPromiseRejections = 1;
3333

34-
// Queue size for each tick array. Must be a factor of two.
34+
// Queue size for each tick array. Must be a power of two.
3535
const kQueueSize = 2048;
3636
const kQueueMask = kQueueSize - 1;
3737

38+
// The next tick queue is implemented as a singly-linked list of fixed-size
39+
// circular buffers. It looks something like this:
40+
//
41+
// head tail
42+
// | |
43+
// v v
44+
// +-----------+ <-----\ +-----------+ <------\ +-----------+
45+
// | [null] | \----- | next | \------- | next |
46+
// +-----------+ +-----------+ +-----------+
47+
// | tick | <-- bottom | tick | <-- bottom | [empty] |
48+
// | tick | | tick | | [empty] |
49+
// | tick | | tick | | [empty] |
50+
// | tick | | tick | | [empty] |
51+
// | tick | | tick | bottom --> | tick |
52+
// | tick | | tick | | tick |
53+
// | ... | | ... | | ... |
54+
// | tick | | tick | | tick |
55+
// | tick | | tick | | tick |
56+
// | [empty] | <-- top | tick | | tick |
57+
// | [empty] | | tick | | tick |
58+
// | [empty] | | tick | | tick |
59+
// +-----------+ +-----------+ <-- top top --> +-----------+
60+
//
61+
// Or, if there is only one fixed-size queue, it looks something
62+
// like either of these:
63+
//
64+
// head tail head tail
65+
// | | | |
66+
// v v v v
67+
// +-----------+ +-----------+
68+
// | [null] | | [null] |
69+
// +-----------+ +-----------+
70+
// | [empty] | | tick |
71+
// | [empty] | | tick |
72+
// | tick | <-- bottom top --> | [empty] |
73+
// | tick | | [empty] |
74+
// | [empty] | <-- top bottom --> | tick |
75+
// | [empty] | | tick |
76+
// +-----------+ +-----------+
77+
//
78+
// Adding a value means moving `top` forward by one, removing means
79+
// moving `bottom` forward by one.
80+
//
81+
// We let `bottom` and `top` wrap around, so when `top` is conceptually
82+
// pointing to the end of the list, that means that the actual value is `0`.
83+
//
84+
// In particular, when `top === bottom`, this can mean *either* that the
85+
// current queue is empty or that it is full. We can differentiate by
86+
// checking whether an entry in the queue is empty (a.k.a. `=== undefined`).
87+
3888
class FixedQueue {
3989
constructor() {
4090
this.bottom = 0;
@@ -49,11 +99,12 @@ function setupNextTick() {
4999
}
50100

51101
shift() {
52-
const next = this.list[this.bottom];
53-
if (next === undefined) return null;
102+
const nextItem = this.list[this.bottom];
103+
if (nextItem === undefined)
104+
return null;
54105
this.list[this.bottom] = undefined;
55106
this.bottom = (this.bottom + 1) & kQueueMask;
56-
return next;
107+
return nextItem;
57108
}
58109
}
59110

@@ -62,21 +113,34 @@ function setupNextTick() {
62113

63114
function push(data) {
64115
if (head.bottom === head.top) {
65-
if (head.list[head.top] !== undefined)
116+
// Either empty or full:
117+
if (head.list[head.top] !== undefined) {
118+
// It's full: Creates a new queue, sets the old queue's `.next` to it,
119+
// and sets it as the new main queue.
66120
head = head.next = new FixedQueue();
67-
else
121+
} else {
122+
// If the head is empty, that means that it was the only fixed-sized
123+
// queue in existence.
124+
DCHECK_EQ(head.next, null);
125+
// This is the first tick object in existence, so we need to inform
126+
// the C++ side that we do want to run `_tickCallback()`.
68127
tickInfo[kHasScheduled] = 1;
128+
}
69129
}
70130
head.push(data);
71131
}
72132

73133
function shift() {
74134
const next = tail.shift();
75-
if (tail.top === tail.bottom) {
76-
if (tail.next)
135+
if (tail.top === tail.bottom) { // -> .shift() emptied the current queue.
136+
if (tail.next !== null) {
137+
// If there is another queue, it forms the new tail.
77138
tail = tail.next;
78-
else
139+
} else {
140+
// We've just run out of items. Let the native side know that it
141+
// doesn't need to bother calling into JS to run the queue.
79142
tickInfo[kHasScheduled] = 0;
143+
}
80144
}
81145
return next;
82146
}

0 commit comments

Comments
Β (0)