Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d348496

Browse filesBrowse 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 <anna@addaleax.net>
1 parent 738b0a1 commit d348496
Copy full SHA for d348496

File tree

Expand file treeCollapse file tree

4 files changed

+76
-132
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+76
-132
lines changed
Open diff view settings
Collapse file

‎lib/internal/process/next_tick.js‎

Copy file name to clipboard
+49-101Lines 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
}
Collapse file

‎src/env-inl.h‎

Copy file name to clipboardExpand all lines: src/env-inl.h
+3-11Lines changed: 3 additions & 11 deletions
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,
Collapse file

‎src/env.h‎

Copy file name to clipboardExpand all lines: src/env.h
+4-7Lines changed: 4 additions & 7 deletions
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
};
Collapse file

‎src/node.cc‎

Copy file name to clipboardExpand all lines: src/node.cc
+20-13Lines changed: 20 additions & 13 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.