Skip to content

Commit a45f3f8

Browse files
addaleaxtargos
authored andcommitted
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 d3d1ee7 commit a45f3f8

File tree

1 file changed

+73
-9
lines changed

1 file changed

+73
-9
lines changed

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

+73-9
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,60 @@ function setupNextTick() {
3232
const kHasScheduled = 0;
3333
const kHasPromiseRejections = 1;
3434

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

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

52102
shift() {
53-
const next = this.list[this.bottom];
54-
if (next === undefined) return null;
103+
const nextItem = this.list[this.bottom];
104+
if (nextItem === undefined)
105+
return null;
55106
this.list[this.bottom] = undefined;
56107
this.bottom = (this.bottom + 1) & kQueueMask;
57-
return next;
108+
return nextItem;
58109
}
59110
}
60111

@@ -63,21 +114,34 @@ function setupNextTick() {
63114

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

74134
function shift() {
75135
const next = tail.shift();
76-
if (tail.top === tail.bottom) {
77-
if (tail.next)
136+
if (tail.top === tail.bottom) { // -> .shift() emptied the current queue.
137+
if (tail.next !== null) {
138+
// If there is another queue, it forms the new tail.
78139
tail = tail.next;
79-
else
140+
} else {
141+
// We've just run out of items. Let the native side know that it
142+
// doesn't need to bother calling into JS to run the queue.
80143
tickInfo[kHasScheduled] = 0;
144+
}
81145
}
82146
return next;
83147
}

0 commit comments

Comments
Β (0)