Skip to content

Commit 8abcc76

Browse files
joyeecheungaddaleax
authored andcommitted
src: move process.nextTick and promise setup into node_task_queue.cc
This patch: - Moves the process.nextTick and promise setup C++ code into node_task_queue.cc which is exposed as `internalBinding('task_queue')` - Makes `lib/internal/process/promises.js` and `lib/internal/process/next_tick.js` as side-effect-free as possible - Removes the bootstrapper object being passed into `bootstrap/node.js`, let `next_tick.js` and `promises.js` load whatever they need from `internalBinding('task_queue')` instead. - Rename `process._tickCallback` to `runNextTicks` internally for clarity but still expose it as `process._tickCallback`. PR-URL: #25163 Refs: #24961 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]>
1 parent 733af61 commit 8abcc76

13 files changed

+199
-192
lines changed

lib/internal/bootstrap/node.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@
1414

1515
// This file is compiled as if it's wrapped in a function with arguments
1616
// passed by node::LoadEnvironment()
17-
/* global process, bootstrappers, loaderExports, triggerFatalException */
17+
/* global process, loaderExports, triggerFatalException */
1818
/* global isMainThread */
1919

20-
const {
21-
_setupNextTick,
22-
_setupPromises
23-
} = bootstrappers;
2420
const { internalBinding, NativeModule } = loaderExports;
2521

2622
const exceptionHandlerState = { captureFn: null };
@@ -105,8 +101,18 @@ function startup() {
105101
}
106102

107103
NativeModule.require('internal/process/warning').setup();
108-
NativeModule.require('internal/process/next_tick').setup(_setupNextTick,
109-
_setupPromises);
104+
const {
105+
nextTick,
106+
runNextTicks
107+
} = NativeModule.require('internal/process/next_tick').setup();
108+
109+
process.nextTick = nextTick;
110+
// Used to emulate a tick manually in the JS land.
111+
// A better name for this function would be `runNextTicks` but
112+
// it has been exposed to the process object so we keep this legacy name
113+
// TODO(joyeecheung): either remove it or make it public
114+
process._tickCallback = runNextTicks;
115+
110116
const credentials = internalBinding('credentials');
111117
if (credentials.implementsPosixCredentials) {
112118
process.getuid = credentials.getuid;

lib/internal/process/next_tick.js

+129-119
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,138 @@
11
'use strict';
22

3-
exports.setup = setupNextTick;
4-
5-
function setupNextTick(_setupNextTick, _setupPromises) {
6-
const {
7-
getDefaultTriggerAsyncId,
8-
newAsyncId,
9-
initHooksExist,
10-
destroyHooksExist,
11-
emitInit,
12-
emitBefore,
13-
emitAfter,
14-
emitDestroy,
15-
symbols: { async_id_symbol, trigger_async_id_symbol }
16-
} = require('internal/async_hooks');
17-
const emitPromiseRejectionWarnings =
18-
require('internal/process/promises').setup(_setupPromises);
19-
const { ERR_INVALID_CALLBACK } = require('internal/errors').codes;
20-
const FixedQueue = require('internal/fixed_queue');
21-
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-
] = _setupNextTick(internalTickCallback);
30-
31-
// *Must* match Environment::TickInfo::Fields in src/env.h.
32-
const kHasScheduled = 0;
33-
const kHasPromiseRejections = 1;
34-
35-
const queue = new FixedQueue();
36-
37-
process.nextTick = nextTick;
38-
// Needs to be accessible from beyond this scope.
39-
process._tickCallback = _tickCallback;
40-
41-
function _tickCallback() {
42-
if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0)
43-
runMicrotasks();
44-
if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0)
45-
return;
46-
47-
internalTickCallback();
48-
}
49-
50-
function internalTickCallback() {
51-
let tock;
52-
do {
53-
while (tock = queue.shift()) {
54-
const asyncId = tock[async_id_symbol];
55-
emitBefore(asyncId, tock[trigger_async_id_symbol]);
56-
// emitDestroy() places the async_id_symbol into an asynchronous queue
57-
// that calls the destroy callback in the future. It's called before
58-
// calling tock.callback so destroy will be called even if the callback
59-
// throws an exception that is handled by 'uncaughtException' or a
60-
// domain.
61-
// TODO(trevnorris): This is a bit of a hack. It relies on the fact
62-
// that nextTick() doesn't allow the event loop to proceed, but if
63-
// any async hooks are enabled during the callback's execution then
64-
// this tock's after hook will be called, but not its destroy hook.
65-
if (destroyHooksExist())
66-
emitDestroy(asyncId);
67-
68-
const callback = tock.callback;
69-
if (tock.args === undefined)
70-
callback();
71-
else
72-
Reflect.apply(callback, undefined, tock.args);
73-
74-
emitAfter(asyncId);
75-
}
76-
tickInfo[kHasScheduled] = 0;
77-
runMicrotasks();
78-
} while (!queue.isEmpty() || emitPromiseRejectionWarnings());
79-
tickInfo[kHasPromiseRejections] = 0;
80-
}
3+
const {
4+
// For easy access to the nextTick state in the C++ land,
5+
// and to avoid unnecessary calls into JS land.
6+
tickInfo,
7+
// Used to run V8's micro task queue.
8+
runMicrotasks,
9+
setTickCallback,
10+
initializePromiseRejectCallback
11+
} = internalBinding('task_queue');
12+
13+
const {
14+
promiseRejectHandler,
15+
emitPromiseRejectionWarnings
16+
} = require('internal/process/promises');
17+
18+
const {
19+
getDefaultTriggerAsyncId,
20+
newAsyncId,
21+
initHooksExist,
22+
destroyHooksExist,
23+
emitInit,
24+
emitBefore,
25+
emitAfter,
26+
emitDestroy,
27+
symbols: { async_id_symbol, trigger_async_id_symbol }
28+
} = require('internal/async_hooks');
29+
const { ERR_INVALID_CALLBACK } = require('internal/errors').codes;
30+
const FixedQueue = require('internal/fixed_queue');
31+
32+
// *Must* match Environment::TickInfo::Fields in src/env.h.
33+
const kHasScheduled = 0;
34+
const kHasPromiseRejections = 1;
35+
36+
const queue = new FixedQueue();
37+
38+
function runNextTicks() {
39+
if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0)
40+
runMicrotasks();
41+
if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0)
42+
return;
43+
44+
internalTickCallback();
45+
}
8146

82-
class TickObject {
83-
constructor(callback, args, triggerAsyncId) {
84-
// This must be set to null first to avoid function tracking
85-
// on the hidden class, revisit in V8 versions after 6.2
86-
this.callback = null;
87-
this.callback = callback;
88-
this.args = args;
89-
90-
const asyncId = newAsyncId();
91-
this[async_id_symbol] = asyncId;
92-
this[trigger_async_id_symbol] = triggerAsyncId;
93-
94-
if (initHooksExist()) {
95-
emitInit(asyncId,
96-
'TickObject',
97-
triggerAsyncId,
98-
this);
99-
}
47+
function internalTickCallback() {
48+
let tock;
49+
do {
50+
while (tock = queue.shift()) {
51+
const asyncId = tock[async_id_symbol];
52+
emitBefore(asyncId, tock[trigger_async_id_symbol]);
53+
// emitDestroy() places the async_id_symbol into an asynchronous queue
54+
// that calls the destroy callback in the future. It's called before
55+
// calling tock.callback so destroy will be called even if the callback
56+
// throws an exception that is handled by 'uncaughtException' or a
57+
// domain.
58+
// TODO(trevnorris): This is a bit of a hack. It relies on the fact
59+
// that nextTick() doesn't allow the event loop to proceed, but if
60+
// any async hooks are enabled during the callback's execution then
61+
// this tock's after hook will be called, but not its destroy hook.
62+
if (destroyHooksExist())
63+
emitDestroy(asyncId);
64+
65+
const callback = tock.callback;
66+
if (tock.args === undefined)
67+
callback();
68+
else
69+
Reflect.apply(callback, undefined, tock.args);
70+
71+
emitAfter(asyncId);
10072
}
101-
}
73+
tickInfo[kHasScheduled] = 0;
74+
runMicrotasks();
75+
} while (!queue.isEmpty() || emitPromiseRejectionWarnings());
76+
tickInfo[kHasPromiseRejections] = 0;
77+
}
10278

103-
// `nextTick()` will not enqueue any callback when the process is about to
104-
// exit since the callback would not have a chance to be executed.
105-
function nextTick(callback) {
106-
if (typeof callback !== 'function')
107-
throw new ERR_INVALID_CALLBACK();
108-
109-
if (process._exiting)
110-
return;
111-
112-
var args;
113-
switch (arguments.length) {
114-
case 1: break;
115-
case 2: args = [arguments[1]]; break;
116-
case 3: args = [arguments[1], arguments[2]]; break;
117-
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
118-
default:
119-
args = new Array(arguments.length - 1);
120-
for (var i = 1; i < arguments.length; i++)
121-
args[i - 1] = arguments[i];
79+
class TickObject {
80+
constructor(callback, args, triggerAsyncId) {
81+
// This must be set to null first to avoid function tracking
82+
// on the hidden class, revisit in V8 versions after 6.2
83+
this.callback = null;
84+
this.callback = callback;
85+
this.args = args;
86+
87+
const asyncId = newAsyncId();
88+
this[async_id_symbol] = asyncId;
89+
this[trigger_async_id_symbol] = triggerAsyncId;
90+
91+
if (initHooksExist()) {
92+
emitInit(asyncId,
93+
'TickObject',
94+
triggerAsyncId,
95+
this);
12296
}
97+
}
98+
}
12399

124-
if (queue.isEmpty())
125-
tickInfo[kHasScheduled] = 1;
126-
queue.push(new TickObject(callback, args, getDefaultTriggerAsyncId()));
100+
// `nextTick()` will not enqueue any callback when the process is about to
101+
// exit since the callback would not have a chance to be executed.
102+
function nextTick(callback) {
103+
if (typeof callback !== 'function')
104+
throw new ERR_INVALID_CALLBACK();
105+
106+
if (process._exiting)
107+
return;
108+
109+
var args;
110+
switch (arguments.length) {
111+
case 1: break;
112+
case 2: args = [arguments[1]]; break;
113+
case 3: args = [arguments[1], arguments[2]]; break;
114+
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
115+
default:
116+
args = new Array(arguments.length - 1);
117+
for (var i = 1; i < arguments.length; i++)
118+
args[i - 1] = arguments[i];
127119
}
120+
121+
if (queue.isEmpty())
122+
tickInfo[kHasScheduled] = 1;
123+
queue.push(new TickObject(callback, args, getDefaultTriggerAsyncId()));
128124
}
125+
126+
// TODO(joyeecheung): make this a factory class so that node.js can
127+
// control the side effects caused by the initializers.
128+
exports.setup = function() {
129+
// Initializes the per-isolate promise rejection callback which
130+
// will call the handler being passed into this function.
131+
initializePromiseRejectCallback(promiseRejectHandler);
132+
// Sets the callback to be run in every tick.
133+
setTickCallback(internalTickCallback);
134+
return {
135+
nextTick,
136+
runNextTicks
137+
};
138+
};

lib/internal/process/promises.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
'use strict';
22

33
const { safeToString } = internalBinding('util');
4+
const {
5+
promiseRejectEvents
6+
} = internalBinding('task_queue');
47

58
const maybeUnhandledPromises = new WeakMap();
69
const pendingUnhandledRejections = [];
710
const asyncHandledRejections = [];
8-
const promiseRejectEvents = {};
911
let lastPromiseId = 0;
1012

11-
exports.setup = setupPromises;
12-
13-
function setupPromises(_setupPromises) {
14-
_setupPromises(handler, promiseRejectEvents);
15-
return emitPromiseRejectionWarnings;
16-
}
17-
18-
function handler(type, promise, reason) {
13+
function promiseRejectHandler(type, promise, reason) {
1914
switch (type) {
2015
case promiseRejectEvents.kPromiseRejectWithNoHandler:
2116
return unhandledRejection(promise, reason);
@@ -124,3 +119,8 @@ function emitPromiseRejectionWarnings() {
124119
}
125120
return maybeScheduledTicks || pendingUnhandledRejections.length !== 0;
126121
}
122+
123+
module.exports = {
124+
promiseRejectHandler,
125+
emitPromiseRejectionWarnings
126+
};

node.gyp

+1-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,6 @@
327327

328328
'sources': [
329329
'src/async_wrap.cc',
330-
'src/bootstrapper.cc',
331330
'src/callback_scope.cc',
332331
'src/cares_wrap.cc',
333332
'src/connect_wrap.cc',
@@ -374,6 +373,7 @@
374373
'src/node_serdes.cc',
375374
'src/node_stat_watcher.cc',
376375
'src/node_symbols.cc',
376+
'src/node_task_queue.cc',
377377
'src/node_trace_events.cc',
378378
'src/node_types.cc',
379379
'src/node_url.cc',

src/env.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
362362
V(performance_entry_template, v8::Function) \
363363
V(pipe_constructor_template, v8::FunctionTemplate) \
364364
V(process_object, v8::Object) \
365-
V(promise_handler_function, v8::Function) \
365+
V(promise_reject_callback, v8::Function) \
366366
V(promise_wrap_template, v8::ObjectTemplate) \
367367
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
368368
V(script_context_constructor_template, v8::FunctionTemplate) \
@@ -377,7 +377,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
377377
V(tty_constructor_template, v8::FunctionTemplate) \
378378
V(udp_constructor_function, v8::Function) \
379379
V(url_constructor_function, v8::Function) \
380-
V(write_wrap_template, v8::ObjectTemplate) \
380+
V(write_wrap_template, v8::ObjectTemplate)
381381

382382
class Environment;
383383

src/node.cc

-6
Original file line numberDiff line numberDiff line change
@@ -1163,20 +1163,14 @@ void LoadEnvironment(Environment* env) {
11631163
return;
11641164
}
11651165

1166-
// Bootstrap Node.js
1167-
Local<Object> bootstrapper = Object::New(env->isolate());
1168-
SetupBootstrapObject(env, bootstrapper);
1169-
11701166
// process, bootstrappers, loaderExports, triggerFatalException
11711167
std::vector<Local<String>> node_params = {
11721168
env->process_string(),
1173-
FIXED_ONE_BYTE_STRING(isolate, "bootstrappers"),
11741169
FIXED_ONE_BYTE_STRING(isolate, "loaderExports"),
11751170
FIXED_ONE_BYTE_STRING(isolate, "triggerFatalException"),
11761171
FIXED_ONE_BYTE_STRING(isolate, "isMainThread")};
11771172
std::vector<Local<Value>> node_args = {
11781173
process,
1179-
bootstrapper,
11801174
loader_exports.ToLocalChecked(),
11811175
env->NewFunctionTemplate(FatalException)
11821176
->GetFunction(context)

src/node_binding.cc

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
V(stream_wrap) \
5353
V(string_decoder) \
5454
V(symbols) \
55+
V(task_queue) \
5556
V(tcp_wrap) \
5657
V(timers) \
5758
V(trace_events) \

0 commit comments

Comments
 (0)