Skip to content

Commit d348496

Browse files
apapirovskiMylesBorins
authored andcommitted
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. Backport-PR-URL: #19006 PR-URL: #17738 Reviewed-By: Anna Henningsen <[email protected]>
1 parent 738b0a1 commit d348496

File tree

4 files changed

+76
-132
lines changed

4 files changed

+76
-132
lines changed

lib/internal/process/next_tick.js

+49-101
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

+3-11
Original file line numberDiff line numberDiff line change
@@ -222,24 +222,16 @@ inline Environment::TickInfo::TickInfo() {
222222
fields_[i] = 0;
223223
}
224224

225-
inline uint32_t* Environment::TickInfo::fields() {
225+
inline uint8_t* Environment::TickInfo::fields() {
226226
return fields_;
227227
}
228228

229229
inline int Environment::TickInfo::fields_count() const {
230230
return kFieldsCount;
231231
}
232232

233-
inline uint32_t Environment::TickInfo::index() const {
234-
return fields_[kIndex];
235-
}
236-
237-
inline uint32_t Environment::TickInfo::length() const {
238-
return fields_[kLength];
239-
}
240-
241-
inline void Environment::TickInfo::set_index(uint32_t value) {
242-
fields_[kIndex] = value;
233+
inline uint8_t Environment::TickInfo::scheduled() const {
234+
return fields_[kScheduled];
243235
}
244236

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

src/env.h

+4-7
Original file line numberDiff line numberDiff line change
@@ -453,23 +453,20 @@ class Environment {
453453

454454
class TickInfo {
455455
public:
456-
inline uint32_t* fields();
456+
inline uint8_t* fields();
457457
inline int fields_count() const;
458-
inline uint32_t index() const;
459-
inline uint32_t length() const;
460-
inline void set_index(uint32_t value);
458+
inline uint8_t scheduled() const;
461459

462460
private:
463461
friend class Environment; // So we can call the constructor.
464462
inline TickInfo();
465463

466464
enum Fields {
467-
kIndex,
468-
kLength,
465+
kScheduled,
469466
kFieldsCount
470467
};
471468

472-
uint32_t fields_[kFieldsCount];
469+
uint8_t fields_[kFieldsCount];
473470

474471
DISALLOW_COPY_AND_ASSIGN(TickInfo);
475472
};

src/node.cc

+20-13
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ using v8::SealHandleScope;
169169
using v8::String;
170170
using v8::TryCatch;
171171
using v8::Uint32Array;
172+
using v8::Uint8Array;
172173
using v8::Undefined;
173174
using v8::V8;
174175
using v8::Value;
@@ -1144,25 +1145,32 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
11441145
Environment* env = Environment::GetCurrent(args);
11451146

11461147
CHECK(args[0]->IsFunction());
1147-
CHECK(args[1]->IsObject());
11481148

11491149
env->set_tick_callback_function(args[0].As<Function>());
11501150

1151-
env->SetMethod(args[1].As<Object>(), "runMicrotasks", RunMicrotasks);
1152-
1153-
// Do a little housekeeping.
11541151
env->process_object()->Delete(
11551152
env->context(),
1156-
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick")).FromJust();
1153+
FIXED_ONE_BYTE_STRING(env->isolate(), "_setupNextTick")).FromJust();
11571154

11581155
// Values use to cross communicate with processNextTick.
1159-
uint32_t* const fields = env->tick_info()->fields();
1160-
uint32_t const fields_count = env->tick_info()->fields_count();
1156+
uint8_t* const fields = env->tick_info()->fields();
1157+
uint8_t const fields_count = env->tick_info()->fields_count();
11611158

11621159
Local<ArrayBuffer> array_buffer =
11631160
ArrayBuffer::New(env->isolate(), fields, sizeof(*fields) * fields_count);
11641161

1165-
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count));
1162+
v8::Local<v8::Function> run_microtasks_fn =
1163+
env->NewFunctionTemplate(RunMicrotasks)->GetFunction(env->context())
1164+
.ToLocalChecked();
1165+
run_microtasks_fn->SetName(
1166+
FIXED_ONE_BYTE_STRING(env->isolate(), "runMicrotasks"));
1167+
1168+
Local<Array> ret = Array::New(env->isolate(), 2);
1169+
ret->Set(env->context(), 0,
1170+
Uint8Array::New(array_buffer, 0, fields_count)).FromJust();
1171+
ret->Set(env->context(), 1, run_microtasks_fn).FromJust();
1172+
1173+
args.GetReturnValue().Set(ret);
11661174
}
11671175

11681176
void PromiseRejectCallback(PromiseRejectMessage message) {
@@ -1278,7 +1286,7 @@ void InternalCallbackScope::Close() {
12781286

12791287
Environment::TickInfo* tick_info = env_->tick_info();
12801288

1281-
if (tick_info->length() == 0) {
1289+
if (tick_info->scheduled() == 0) {
12821290
env_->isolate()->RunMicrotasks();
12831291
}
12841292

@@ -1289,10 +1297,7 @@ void InternalCallbackScope::Close() {
12891297
CHECK_EQ(env_->trigger_async_id(), 0);
12901298
}
12911299

1292-
Local<Object> process = env_->process_object();
1293-
1294-
if (tick_info->length() == 0) {
1295-
tick_info->set_index(0);
1300+
if (tick_info->scheduled() == 0) {
12961301
return;
12971302
}
12981303

@@ -1301,6 +1306,8 @@ void InternalCallbackScope::Close() {
13011306
CHECK_EQ(env_->trigger_async_id(), 0);
13021307
}
13031308

1309+
Local<Object> process = env_->process_object();
1310+
13041311
if (env_->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) {
13051312
failed_ = true;
13061313
}

0 commit comments

Comments
 (0)