Skip to content

Commit

Permalink
src: move process.nextTick and promise setup into node_task_queue.cc
Browse files Browse the repository at this point in the history
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 <ruben@bridgewater.de>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
  • Loading branch information
joyeecheung authored and BridgeAR committed Jan 17, 2019
1 parent fa74cd3 commit 2e33ad1
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 192 deletions.
20 changes: 13 additions & 7 deletions lib/internal/bootstrap/node.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@


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


const {
_setupNextTick,
_setupPromises
} = bootstrappers;
const { internalBinding, NativeModule } = loaderExports; const { internalBinding, NativeModule } = loaderExports;


const exceptionHandlerState = { captureFn: null }; const exceptionHandlerState = { captureFn: null };
Expand Down Expand Up @@ -105,8 +101,18 @@ function startup() {
} }


NativeModule.require('internal/process/warning').setup(); NativeModule.require('internal/process/warning').setup();
NativeModule.require('internal/process/next_tick').setup(_setupNextTick, const {
_setupPromises); nextTick,
runNextTicks
} = NativeModule.require('internal/process/next_tick').setup();

process.nextTick = nextTick;
// Used to emulate a tick manually in the JS land.
// A better name for this function would be `runNextTicks` but
// it has been exposed to the process object so we keep this legacy name
// TODO(joyeecheung): either remove it or make it public
process._tickCallback = runNextTicks;

const credentials = internalBinding('credentials'); const credentials = internalBinding('credentials');
if (credentials.implementsPosixCredentials) { if (credentials.implementsPosixCredentials) {
process.getuid = credentials.getuid; process.getuid = credentials.getuid;
Expand Down
248 changes: 129 additions & 119 deletions lib/internal/process/next_tick.js
Original file line number Original file line Diff line number Diff line change
@@ -1,128 +1,138 @@
'use strict'; 'use strict';


exports.setup = setupNextTick; const {

// For easy access to the nextTick state in the C++ land,
function setupNextTick(_setupNextTick, _setupPromises) { // and to avoid unnecessary calls into JS land.
const { tickInfo,
getDefaultTriggerAsyncId, // Used to run V8's micro task queue.
newAsyncId, runMicrotasks,
initHooksExist, setTickCallback,
destroyHooksExist, initializePromiseRejectCallback
emitInit, } = internalBinding('task_queue');
emitBefore,
emitAfter, const {
emitDestroy, promiseRejectHandler,
symbols: { async_id_symbol, trigger_async_id_symbol } emitPromiseRejectionWarnings
} = require('internal/async_hooks'); } = require('internal/process/promises');
const emitPromiseRejectionWarnings =
require('internal/process/promises').setup(_setupPromises); const {
const { ERR_INVALID_CALLBACK } = require('internal/errors').codes; getDefaultTriggerAsyncId,
const FixedQueue = require('internal/fixed_queue'); newAsyncId,

initHooksExist,
// tickInfo is used so that the C++ code in src/node.cc can destroyHooksExist,
// have easy access to our nextTick state, and avoid unnecessary emitInit,
// calls into JS land. emitBefore,
// runMicrotasks is used to run V8's micro task queue. emitAfter,
const [ emitDestroy,
tickInfo, symbols: { async_id_symbol, trigger_async_id_symbol }
runMicrotasks } = require('internal/async_hooks');
] = _setupNextTick(internalTickCallback); const { ERR_INVALID_CALLBACK } = require('internal/errors').codes;

const FixedQueue = require('internal/fixed_queue');
// *Must* match Environment::TickInfo::Fields in src/env.h.
const kHasScheduled = 0; // *Must* match Environment::TickInfo::Fields in src/env.h.
const kHasPromiseRejections = 1; const kHasScheduled = 0;

const kHasPromiseRejections = 1;
const queue = new FixedQueue();

const queue = new FixedQueue();
process.nextTick = nextTick;
// Needs to be accessible from beyond this scope. function runNextTicks() {
process._tickCallback = _tickCallback; if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0)

runMicrotasks();
function _tickCallback() { if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0)
if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0) return;
runMicrotasks();
if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0) internalTickCallback();
return; }

internalTickCallback();
}

function internalTickCallback() {
let tock;
do {
while (tock = queue.shift()) {
const asyncId = tock[async_id_symbol];
emitBefore(asyncId, tock[trigger_async_id_symbol]);
// emitDestroy() places the async_id_symbol into an asynchronous queue
// that calls the destroy callback in the future. It's called before
// calling tock.callback so destroy will be called even if the callback
// throws an exception that is handled by 'uncaughtException' or a
// domain.
// TODO(trevnorris): This is a bit of a hack. It relies on the fact
// that nextTick() doesn't allow the event loop to proceed, but if
// any async hooks are enabled during the callback's execution then
// this tock's after hook will be called, but not its destroy hook.
if (destroyHooksExist())
emitDestroy(asyncId);

const callback = tock.callback;
if (tock.args === undefined)
callback();
else
Reflect.apply(callback, undefined, tock.args);

emitAfter(asyncId);
}
tickInfo[kHasScheduled] = 0;
runMicrotasks();
} while (!queue.isEmpty() || emitPromiseRejectionWarnings());
tickInfo[kHasPromiseRejections] = 0;
}


class TickObject { function internalTickCallback() {
constructor(callback, args, triggerAsyncId) { let tock;
// This must be set to null first to avoid function tracking do {
// on the hidden class, revisit in V8 versions after 6.2 while (tock = queue.shift()) {
this.callback = null; const asyncId = tock[async_id_symbol];
this.callback = callback; emitBefore(asyncId, tock[trigger_async_id_symbol]);
this.args = args; // emitDestroy() places the async_id_symbol into an asynchronous queue

// that calls the destroy callback in the future. It's called before
const asyncId = newAsyncId(); // calling tock.callback so destroy will be called even if the callback
this[async_id_symbol] = asyncId; // throws an exception that is handled by 'uncaughtException' or a
this[trigger_async_id_symbol] = triggerAsyncId; // domain.

// TODO(trevnorris): This is a bit of a hack. It relies on the fact
if (initHooksExist()) { // that nextTick() doesn't allow the event loop to proceed, but if
emitInit(asyncId, // any async hooks are enabled during the callback's execution then
'TickObject', // this tock's after hook will be called, but not its destroy hook.
triggerAsyncId, if (destroyHooksExist())
this); emitDestroy(asyncId);
}
const callback = tock.callback;
if (tock.args === undefined)
callback();
else
Reflect.apply(callback, undefined, tock.args);

emitAfter(asyncId);
} }
} tickInfo[kHasScheduled] = 0;
runMicrotasks();
} while (!queue.isEmpty() || emitPromiseRejectionWarnings());
tickInfo[kHasPromiseRejections] = 0;
}


// `nextTick()` will not enqueue any callback when the process is about to class TickObject {
// exit since the callback would not have a chance to be executed. constructor(callback, args, triggerAsyncId) {
function nextTick(callback) { // This must be set to null first to avoid function tracking
if (typeof callback !== 'function') // on the hidden class, revisit in V8 versions after 6.2
throw new ERR_INVALID_CALLBACK(); this.callback = null;

this.callback = callback;
if (process._exiting) this.args = args;
return;

const asyncId = newAsyncId();
var args; this[async_id_symbol] = asyncId;
switch (arguments.length) { this[trigger_async_id_symbol] = triggerAsyncId;
case 1: break;
case 2: args = [arguments[1]]; break; if (initHooksExist()) {
case 3: args = [arguments[1], arguments[2]]; break; emitInit(asyncId,
case 4: args = [arguments[1], arguments[2], arguments[3]]; break; 'TickObject',
default: triggerAsyncId,
args = new Array(arguments.length - 1); this);
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
} }
}
}


if (queue.isEmpty()) // `nextTick()` will not enqueue any callback when the process is about to
tickInfo[kHasScheduled] = 1; // exit since the callback would not have a chance to be executed.
queue.push(new TickObject(callback, args, getDefaultTriggerAsyncId())); function nextTick(callback) {
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK();

if (process._exiting)
return;

var args;
switch (arguments.length) {
case 1: break;
case 2: args = [arguments[1]]; break;
case 3: args = [arguments[1], arguments[2]]; break;
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
default:
args = new Array(arguments.length - 1);
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
} }

if (queue.isEmpty())
tickInfo[kHasScheduled] = 1;
queue.push(new TickObject(callback, args, getDefaultTriggerAsyncId()));
} }

// TODO(joyeecheung): make this a factory class so that node.js can
// control the side effects caused by the initializers.
exports.setup = function() {
// Initializes the per-isolate promise rejection callback which
// will call the handler being passed into this function.
initializePromiseRejectCallback(promiseRejectHandler);
// Sets the callback to be run in every tick.
setTickCallback(internalTickCallback);
return {
nextTick,
runNextTicks
};
};
18 changes: 9 additions & 9 deletions lib/internal/process/promises.js
Original file line number Original file line Diff line number Diff line change
@@ -1,21 +1,16 @@
'use strict'; 'use strict';


const { safeToString } = internalBinding('util'); const { safeToString } = internalBinding('util');
const {
promiseRejectEvents
} = internalBinding('task_queue');


const maybeUnhandledPromises = new WeakMap(); const maybeUnhandledPromises = new WeakMap();
const pendingUnhandledRejections = []; const pendingUnhandledRejections = [];
const asyncHandledRejections = []; const asyncHandledRejections = [];
const promiseRejectEvents = {};
let lastPromiseId = 0; let lastPromiseId = 0;


exports.setup = setupPromises; function promiseRejectHandler(type, promise, reason) {

function setupPromises(_setupPromises) {
_setupPromises(handler, promiseRejectEvents);
return emitPromiseRejectionWarnings;
}

function handler(type, promise, reason) {
switch (type) { switch (type) {
case promiseRejectEvents.kPromiseRejectWithNoHandler: case promiseRejectEvents.kPromiseRejectWithNoHandler:
return unhandledRejection(promise, reason); return unhandledRejection(promise, reason);
Expand Down Expand Up @@ -124,3 +119,8 @@ function emitPromiseRejectionWarnings() {
} }
return maybeScheduledTicks || pendingUnhandledRejections.length !== 0; return maybeScheduledTicks || pendingUnhandledRejections.length !== 0;
} }

module.exports = {
promiseRejectHandler,
emitPromiseRejectionWarnings
};
2 changes: 1 addition & 1 deletion node.gyp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@


'sources': [ 'sources': [
'src/async_wrap.cc', 'src/async_wrap.cc',
'src/bootstrapper.cc',
'src/callback_scope.cc', 'src/callback_scope.cc',
'src/cares_wrap.cc', 'src/cares_wrap.cc',
'src/connect_wrap.cc', 'src/connect_wrap.cc',
Expand Down Expand Up @@ -374,6 +373,7 @@
'src/node_serdes.cc', 'src/node_serdes.cc',
'src/node_stat_watcher.cc', 'src/node_stat_watcher.cc',
'src/node_symbols.cc', 'src/node_symbols.cc',
'src/node_task_queue.cc',
'src/node_trace_events.cc', 'src/node_trace_events.cc',
'src/node_types.cc', 'src/node_types.cc',
'src/node_url.cc', 'src/node_url.cc',
Expand Down
4 changes: 2 additions & 2 deletions src/env.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(performance_entry_template, v8::Function) \ V(performance_entry_template, v8::Function) \
V(pipe_constructor_template, v8::FunctionTemplate) \ V(pipe_constructor_template, v8::FunctionTemplate) \
V(process_object, v8::Object) \ V(process_object, v8::Object) \
V(promise_handler_function, v8::Function) \ V(promise_reject_callback, v8::Function) \
V(promise_wrap_template, v8::ObjectTemplate) \ V(promise_wrap_template, v8::ObjectTemplate) \
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \ V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
V(script_context_constructor_template, v8::FunctionTemplate) \ V(script_context_constructor_template, v8::FunctionTemplate) \
Expand All @@ -377,7 +377,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(tty_constructor_template, v8::FunctionTemplate) \ V(tty_constructor_template, v8::FunctionTemplate) \
V(udp_constructor_function, v8::Function) \ V(udp_constructor_function, v8::Function) \
V(url_constructor_function, v8::Function) \ V(url_constructor_function, v8::Function) \
V(write_wrap_template, v8::ObjectTemplate) \ V(write_wrap_template, v8::ObjectTemplate)


class Environment; class Environment;


Expand Down
6 changes: 0 additions & 6 deletions src/node.cc
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1163,20 +1163,14 @@ void LoadEnvironment(Environment* env) {
return; return;
} }


// Bootstrap Node.js
Local<Object> bootstrapper = Object::New(env->isolate());
SetupBootstrapObject(env, bootstrapper);

// process, bootstrappers, loaderExports, triggerFatalException // process, bootstrappers, loaderExports, triggerFatalException
std::vector<Local<String>> node_params = { std::vector<Local<String>> node_params = {
env->process_string(), env->process_string(),
FIXED_ONE_BYTE_STRING(isolate, "bootstrappers"),
FIXED_ONE_BYTE_STRING(isolate, "loaderExports"), FIXED_ONE_BYTE_STRING(isolate, "loaderExports"),
FIXED_ONE_BYTE_STRING(isolate, "triggerFatalException"), FIXED_ONE_BYTE_STRING(isolate, "triggerFatalException"),
FIXED_ONE_BYTE_STRING(isolate, "isMainThread")}; FIXED_ONE_BYTE_STRING(isolate, "isMainThread")};
std::vector<Local<Value>> node_args = { std::vector<Local<Value>> node_args = {
process, process,
bootstrapper,
loader_exports.ToLocalChecked(), loader_exports.ToLocalChecked(),
env->NewFunctionTemplate(FatalException) env->NewFunctionTemplate(FatalException)
->GetFunction(context) ->GetFunction(context)
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
V(stream_wrap) \ V(stream_wrap) \
V(string_decoder) \ V(string_decoder) \
V(symbols) \ V(symbols) \
V(task_queue) \
V(tcp_wrap) \ V(tcp_wrap) \
V(timers) \ V(timers) \
V(trace_events) \ V(trace_events) \
Expand Down
Loading

0 comments on commit 2e33ad1

Please sign in to comment.