Skip to content

Commit 4444b6b

Browse files
committed
process: refactor nextTick for clarity
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. PR-URL: #17738 Reviewed-By: Anna Henningsen <[email protected]>
1 parent 35d8ef5 commit 4444b6b

File tree

4 files changed

+76
-132
lines changed

4 files changed

+76
-132
lines changed

lib/internal/process/next_tick.js

Lines changed: 49 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,9 @@
11
'use strict';
22

3-
// This value is used to prevent the nextTickQueue from becoming too
4-
// large and cause the process to run out of memory. When this value
5-
// is reached the nextTimeQueue array will be shortened (see tickDone
6-
// for details).
7-
const kMaxCallbacksPerLoop = 1e4;
8-
93
exports.setup = setupNextTick;
104
// Will be overwritten when setupNextTick() is called.
115
exports.nextTick = null;
126

13-
class NextTickQueue {
14-
constructor() {
15-
this.head = null;
16-
this.tail = null;
17-
}
18-
19-
push(v) {
20-
const entry = { data: v, next: null };
21-
if (this.tail !== null)
22-
this.tail.next = entry;
23-
else
24-
this.head = entry;
25-
this.tail = entry;
26-
}
27-
28-
shift() {
29-
if (this.head === null)
30-
return;
31-
const ret = this.head.data;
32-
if (this.head === this.tail)
33-
this.head = this.tail = null;
34-
else
35-
this.head = this.head.next;
36-
return ret;
37-
}
38-
39-
clear() {
40-
this.head = null;
41-
this.tail = null;
42-
}
43-
}
44-
457
function setupNextTick() {
468
const async_wrap = process.binding('async_wrap');
479
const async_hooks = require('internal/async_hooks');
@@ -56,15 +18,47 @@ function setupNextTick() {
5618
// Grab the constants necessary for working with internal arrays.
5719
const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
5820
const { async_id_symbol, trigger_async_id_symbol } = async_wrap;
59-
const nextTickQueue = new NextTickQueue();
60-
var microtasksScheduled = false;
6121

62-
// Used to run V8's micro task queue.
63-
var _runMicrotasks = {};
22+
// tickInfo is used so that the C++ code in src/node.cc can
23+
// have easy access to our nextTick state, and avoid unnecessary
24+
// calls into JS land.
25+
// runMicrotasks is used to run V8's micro task queue.
26+
const [
27+
tickInfo,
28+
runMicrotasks
29+
] = process._setupNextTick(_tickCallback);
6430

6531
// *Must* match Environment::TickInfo::Fields in src/env.h.
66-
var kIndex = 0;
67-
var kLength = 1;
32+
const kScheduled = 0;
33+
34+
const nextTickQueue = {
35+
head: null,
36+
tail: null,
37+
push(data) {
38+
const entry = { data, next: null };
39+
if (this.tail !== null) {
40+
this.tail.next = entry;
41+
} else {
42+
this.head = entry;
43+
tickInfo[kScheduled] = 1;
44+
}
45+
this.tail = entry;
46+
},
47+
shift() {
48+
if (this.head === null)
49+
return;
50+
const ret = this.head.data;
51+
if (this.head === this.tail) {
52+
this.head = this.tail = null;
53+
tickInfo[kScheduled] = 0;
54+
} else {
55+
this.head = this.head.next;
56+
}
57+
return ret;
58+
}
59+
};
60+
61+
var microtasksScheduled = false;
6862

6963
process.nextTick = nextTick;
7064
// Needs to be accessible from beyond this scope.
@@ -73,25 +67,6 @@ function setupNextTick() {
7367
// Set the nextTick() function for internal usage.
7468
exports.nextTick = internalNextTick;
7569

76-
// This tickInfo thing is used so that the C++ code in src/node.cc
77-
// can have easy access to our nextTick state, and avoid unnecessary
78-
// calls into JS land.
79-
const tickInfo = process._setupNextTick(_tickCallback, _runMicrotasks);
80-
81-
_runMicrotasks = _runMicrotasks.runMicrotasks;
82-
83-
function tickDone() {
84-
if (tickInfo[kLength] !== 0) {
85-
if (tickInfo[kLength] <= tickInfo[kIndex]) {
86-
nextTickQueue.clear();
87-
tickInfo[kLength] = 0;
88-
} else {
89-
tickInfo[kLength] -= tickInfo[kIndex];
90-
}
91-
}
92-
tickInfo[kIndex] = 0;
93-
}
94-
9570
const microTasksTickObject = {
9671
callback: runMicrotasksCallback,
9772
args: undefined,
@@ -105,38 +80,27 @@ function setupNextTick() {
10580
// For the moment all microtasks come from the void until the PromiseHook
10681
// API is implemented.
10782
nextTickQueue.push(microTasksTickObject);
108-
109-
tickInfo[kLength]++;
11083
microtasksScheduled = true;
11184
}
11285

11386
function runMicrotasksCallback() {
11487
microtasksScheduled = false;
115-
_runMicrotasks();
88+
runMicrotasks();
11689

117-
if (tickInfo[kIndex] < tickInfo[kLength] ||
118-
emitPendingUnhandledRejections()) {
90+
if (nextTickQueue.head !== null || emitPendingUnhandledRejections())
11991
scheduleMicrotasks();
120-
}
12192
}
12293

12394
function _tickCallback() {
95+
let tock;
12496
do {
125-
while (tickInfo[kIndex] < tickInfo[kLength]) {
126-
++tickInfo[kIndex];
127-
const tock = nextTickQueue.shift();
128-
129-
// CHECK(Number.isSafeInteger(tock[async_id_symbol]))
130-
// CHECK(tock[async_id_symbol] > 0)
131-
// CHECK(Number.isSafeInteger(tock[trigger_async_id_symbol]))
132-
// CHECK(tock[trigger_async_id_symbol] > 0)
133-
97+
while (tock = nextTickQueue.shift()) {
13498
const asyncId = tock[async_id_symbol];
13599
emitBefore(asyncId, tock[trigger_async_id_symbol]);
136100
// emitDestroy() places the async_id_symbol into an asynchronous queue
137101
// that calls the destroy callback in the future. It's called before
138102
// calling tock.callback so destroy will be called even if the callback
139-
// throws an exception that is handles by 'uncaughtException' or a
103+
// throws an exception that is handled by 'uncaughtException' or a
140104
// domain.
141105
// TODO(trevnorris): This is a bit of a hack. It relies on the fact
142106
// that nextTick() doesn't allow the event loop to proceed, but if
@@ -152,24 +116,21 @@ function setupNextTick() {
152116
Reflect.apply(callback, undefined, tock.args);
153117

154118
emitAfter(asyncId);
155-
156-
if (kMaxCallbacksPerLoop < tickInfo[kIndex])
157-
tickDone();
158119
}
159-
tickDone();
160-
_runMicrotasks();
120+
runMicrotasks();
161121
emitPendingUnhandledRejections();
162-
} while (tickInfo[kLength] !== 0);
122+
} while (nextTickQueue.head !== null);
163123
}
164124

165125
class TickObject {
166-
constructor(callback, args, asyncId, triggerAsyncId) {
126+
constructor(callback, args, triggerAsyncId) {
167127
// this must be set to null first to avoid function tracking
168128
// on the hidden class, revisit in V8 versions after 6.2
169129
this.callback = null;
170130
this.callback = callback;
171131
this.args = args;
172132

133+
const asyncId = ++async_id_fields[kAsyncIdCounter];
173134
this[async_id_symbol] = asyncId;
174135
this[trigger_async_id_symbol] = triggerAsyncId;
175136

@@ -203,13 +164,7 @@ function setupNextTick() {
203164
args[i - 1] = arguments[i];
204165
}
205166

206-
// In V8 6.2, moving tickInfo & async_id_fields[kAsyncIdCounter] into the
207-
// TickObject incurs a significant performance penalty in the
208-
// next-tick-breadth-args benchmark (revisit later)
209-
++tickInfo[kLength];
210-
nextTickQueue.push(new TickObject(callback,
211-
args,
212-
++async_id_fields[kAsyncIdCounter],
167+
nextTickQueue.push(new TickObject(callback, args,
213168
getDefaultTriggerAsyncId()));
214169
}
215170

@@ -238,13 +193,6 @@ function setupNextTick() {
238193

239194
if (triggerAsyncId === null)
240195
triggerAsyncId = getDefaultTriggerAsyncId();
241-
// In V8 6.2, moving tickInfo & async_id_fields[kAsyncIdCounter] into the
242-
// TickObject incurs a significant performance penalty in the
243-
// next-tick-breadth-args benchmark (revisit later)
244-
++tickInfo[kLength];
245-
nextTickQueue.push(new TickObject(callback,
246-
args,
247-
++async_id_fields[kAsyncIdCounter],
248-
triggerAsyncId));
196+
nextTickQueue.push(new TickObject(callback, args, triggerAsyncId));
249197
}
250198
}

src/env-inl.h

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -207,24 +207,16 @@ inline Environment::TickInfo::TickInfo() {
207207
fields_[i] = 0;
208208
}
209209

210-
inline uint32_t* Environment::TickInfo::fields() {
210+
inline uint8_t* Environment::TickInfo::fields() {
211211
return fields_;
212212
}
213213

214214
inline int Environment::TickInfo::fields_count() const {
215215
return kFieldsCount;
216216
}
217217

218-
inline uint32_t Environment::TickInfo::index() const {
219-
return fields_[kIndex];
220-
}
221-
222-
inline uint32_t Environment::TickInfo::length() const {
223-
return fields_[kLength];
224-
}
225-
226-
inline void Environment::TickInfo::set_index(uint32_t value) {
227-
fields_[kIndex] = value;
218+
inline uint8_t Environment::TickInfo::scheduled() const {
219+
return fields_[kScheduled];
228220
}
229221

230222
inline void Environment::AssignToContext(v8::Local<v8::Context> context,

src/env.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,23 +457,20 @@ class Environment {
457457

458458
class TickInfo {
459459
public:
460-
inline uint32_t* fields();
460+
inline uint8_t* fields();
461461
inline int fields_count() const;
462-
inline uint32_t index() const;
463-
inline uint32_t length() const;
464-
inline void set_index(uint32_t value);
462+
inline uint8_t scheduled() const;
465463

466464
private:
467465
friend class Environment; // So we can call the constructor.
468466
inline TickInfo();
469467

470468
enum Fields {
471-
kIndex,
472-
kLength,
469+
kScheduled,
473470
kFieldsCount
474471
};
475472

476-
uint32_t fields_[kFieldsCount];
473+
uint8_t fields_[kFieldsCount];
477474

478475
DISALLOW_COPY_AND_ASSIGN(TickInfo);
479476
};

src/node.cc

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ using v8::SealHandleScope;
168168
using v8::String;
169169
using v8::TryCatch;
170170
using v8::Uint32Array;
171+
using v8::Uint8Array;
171172
using v8::Undefined;
172173
using v8::V8;
173174
using v8::Value;
@@ -867,25 +868,32 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
867868
Environment* env = Environment::GetCurrent(args);
868869

869870
CHECK(args[0]->IsFunction());
870-
CHECK(args[1]->IsObject());
871871

872872
env->set_tick_callback_function(args[0].As<Function>());
873873

874-
env->SetMethod(args[1].As<Object>(), "runMicrotasks", RunMicrotasks);
875-
876-
// Do a little housekeeping.
877874
env->process_object()->Delete(
878875
env->context(),
879-
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick")).FromJust();
876+
FIXED_ONE_BYTE_STRING(env->isolate(), "_setupNextTick")).FromJust();
880877

881878
// Values use to cross communicate with processNextTick.
882-
uint32_t* const fields = env->tick_info()->fields();
883-
uint32_t const fields_count = env->tick_info()->fields_count();
879+
uint8_t* const fields = env->tick_info()->fields();
880+
uint8_t const fields_count = env->tick_info()->fields_count();
884881

885882
Local<ArrayBuffer> array_buffer =
886883
ArrayBuffer::New(env->isolate(), fields, sizeof(*fields) * fields_count);
887884

888-
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count));
885+
v8::Local<v8::Function> run_microtasks_fn =
886+
env->NewFunctionTemplate(RunMicrotasks)->GetFunction(env->context())
887+
.ToLocalChecked();
888+
run_microtasks_fn->SetName(
889+
FIXED_ONE_BYTE_STRING(env->isolate(), "runMicrotasks"));
890+
891+
Local<Array> ret = Array::New(env->isolate(), 2);
892+
ret->Set(env->context(), 0,
893+
Uint8Array::New(array_buffer, 0, fields_count)).FromJust();
894+
ret->Set(env->context(), 1, run_microtasks_fn).FromJust();
895+
896+
args.GetReturnValue().Set(ret);
889897
}
890898

891899
void PromiseRejectCallback(PromiseRejectMessage message) {
@@ -1011,7 +1019,7 @@ void InternalCallbackScope::Close() {
10111019

10121020
Environment::TickInfo* tick_info = env_->tick_info();
10131021

1014-
if (tick_info->length() == 0) {
1022+
if (tick_info->scheduled() == 0) {
10151023
env_->isolate()->RunMicrotasks();
10161024
}
10171025

@@ -1022,10 +1030,7 @@ void InternalCallbackScope::Close() {
10221030
CHECK_EQ(env_->trigger_async_id(), 0);
10231031
}
10241032

1025-
Local<Object> process = env_->process_object();
1026-
1027-
if (tick_info->length() == 0) {
1028-
tick_info->set_index(0);
1033+
if (tick_info->scheduled() == 0) {
10291034
return;
10301035
}
10311036

@@ -1034,6 +1039,8 @@ void InternalCallbackScope::Close() {
10341039
CHECK_EQ(env_->trigger_async_id(), 0);
10351040
}
10361041

1042+
Local<Object> process = env_->process_object();
1043+
10371044
if (env_->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) {
10381045
failed_ = true;
10391046
}

0 commit comments

Comments
 (0)