Skip to content

Commit

Permalink
lib: document nextTick queue internals
Browse files Browse the repository at this point in the history
Make this code (a bit more) comprehensible by adding some
internals docs.

With diagrams and everything! πŸŽ‰

PR-URL: #19469
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Weijia Wang <starkwang@126.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Gus Caplan <me@gus.host>
  • Loading branch information
addaleax committed Mar 25, 2018
1 parent 2d94f77 commit 34a948f
Showing 1 changed file with 73 additions and 9 deletions.
82 changes: 73 additions & 9 deletions lib/internal/process/next_tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,60 @@ function setupNextTick() {
const kHasScheduled = 0;
const kHasPromiseRejections = 1;

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

// The next tick queue is implemented as a singly-linked list of fixed-size
// circular buffers. It looks something like this:
//
// head tail
// | |
// v v
// +-----------+ <-----\ +-----------+ <------\ +-----------+
// | [null] | \----- | next | \------- | next |
// +-----------+ +-----------+ +-----------+
// | tick | <-- bottom | tick | <-- bottom | [empty] |
// | tick | | tick | | [empty] |
// | tick | | tick | | [empty] |
// | tick | | tick | | [empty] |
// | tick | | tick | bottom --> | tick |
// | tick | | tick | | tick |
// | ... | | ... | | ... |
// | tick | | tick | | tick |
// | tick | | tick | | tick |
// | [empty] | <-- top | tick | | tick |
// | [empty] | | tick | | tick |
// | [empty] | | tick | | tick |
// +-----------+ +-----------+ <-- top top --> +-----------+
//
// Or, if there is only one fixed-size queue, it looks something
// like either of these:
//
// head tail head tail
// | | | |
// v v v v
// +-----------+ +-----------+
// | [null] | | [null] |
// +-----------+ +-----------+
// | [empty] | | tick |
// | [empty] | | tick |
// | tick | <-- bottom top --> | [empty] |
// | tick | | [empty] |
// | [empty] | <-- top bottom --> | tick |
// | [empty] | | tick |
// +-----------+ +-----------+
//
// Adding a value means moving `top` forward by one, removing means
// moving `bottom` forward by one.
//
// We let `bottom` and `top` wrap around, so when `top` is conceptually
// pointing to the end of the list, that means that the actual value is `0`.
//
// In particular, when `top === bottom`, this can mean *either* that the
// current queue is empty or that it is full. We can differentiate by
// checking whether an entry in the queue is empty (a.k.a. `=== undefined`).

class FixedQueue {
constructor() {
this.bottom = 0;
Expand All @@ -49,11 +99,12 @@ function setupNextTick() {
}

shift() {
const next = this.list[this.bottom];
if (next === undefined) return null;
const nextItem = this.list[this.bottom];
if (nextItem === undefined)
return null;
this.list[this.bottom] = undefined;
this.bottom = (this.bottom + 1) & kQueueMask;
return next;
return nextItem;
}
}

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

function push(data) {
if (head.bottom === head.top) {
if (head.list[head.top] !== undefined)
// Either empty or full:
if (head.list[head.top] !== undefined) {
// It's full: Creates a new queue, sets the old queue's `.next` to it,
// and sets it as the new main queue.
head = head.next = new FixedQueue();
else
} else {
// If the head is empty, that means that it was the only fixed-sized
// queue in existence.
DCHECK_EQ(head.next, null);
// This is the first tick object in existence, so we need to inform
// the C++ side that we do want to run `_tickCallback()`.
tickInfo[kHasScheduled] = 1;
}
}
head.push(data);
}

function shift() {
const next = tail.shift();
if (tail.top === tail.bottom) {
if (tail.next)
if (tail.top === tail.bottom) { // -> .shift() emptied the current queue.
if (tail.next !== null) {
// If there is another queue, it forms the new tail.
tail = tail.next;
else
} else {
// We've just run out of items. Let the native side know that it
// doesn't need to bother calling into JS to run the queue.
tickInfo[kHasScheduled] = 0;
}
}
return next;
}
Expand Down

1 comment on commit 34a948f

@mafintosh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for documenting that πŸ€—

Please sign in to comment.