diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2018-12-19 18:44:14 +0800 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2018-12-24 07:57:15 +0800 |
commit | 457603e96194b4858ad715f9faacb3ad7fec7f35 (patch) | |
tree | c9d496b06366721b7453b38c176b1cdbaa39fbe9 /lib/internal | |
parent | e830e2742cedceb7fc89de4910b2c1a9536a1638 (diff) | |
download | android-node-v8-457603e96194b4858ad715f9faacb3ad7fec7f35.tar.gz android-node-v8-457603e96194b4858ad715f9faacb3ad7fec7f35.tar.bz2 android-node-v8-457603e96194b4858ad715f9faacb3ad7fec7f35.zip |
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: https://github.com/nodejs/node/pull/25163
Refs: https://github.com/nodejs/node/issues/24961
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Diffstat (limited to 'lib/internal')
-rw-r--r-- | lib/internal/bootstrap/node.js | 20 | ||||
-rw-r--r-- | lib/internal/process/next_tick.js | 248 | ||||
-rw-r--r-- | lib/internal/process/promises.js | 18 |
3 files changed, 151 insertions, 135 deletions
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 0f207ab660..870a54746b 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -14,13 +14,9 @@ // This file is compiled as if it's wrapped in a function with arguments // passed by node::LoadEnvironment() -/* global process, bootstrappers, loaderExports, triggerFatalException */ +/* global process, loaderExports, triggerFatalException */ /* global isMainThread */ -const { - _setupNextTick, - _setupPromises -} = bootstrappers; const { internalBinding, NativeModule } = loaderExports; const exceptionHandlerState = { captureFn: null }; @@ -105,8 +101,18 @@ function startup() { } NativeModule.require('internal/process/warning').setup(); - NativeModule.require('internal/process/next_tick').setup(_setupNextTick, - _setupPromises); + const { + 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'); if (credentials.implementsPosixCredentials) { process.getuid = credentials.getuid; diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index cb0274367a..00bba25471 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -1,128 +1,138 @@ 'use strict'; -exports.setup = setupNextTick; - -function setupNextTick(_setupNextTick, _setupPromises) { - const { - getDefaultTriggerAsyncId, - newAsyncId, - initHooksExist, - destroyHooksExist, - emitInit, - emitBefore, - emitAfter, - emitDestroy, - symbols: { async_id_symbol, trigger_async_id_symbol } - } = require('internal/async_hooks'); - const emitPromiseRejectionWarnings = - require('internal/process/promises').setup(_setupPromises); - const { ERR_INVALID_CALLBACK } = require('internal/errors').codes; - const FixedQueue = require('internal/fixed_queue'); - - // tickInfo is used so that the C++ code in src/node.cc can - // have easy access to our nextTick state, and avoid unnecessary - // calls into JS land. - // runMicrotasks is used to run V8's micro task queue. - const [ - tickInfo, - runMicrotasks - ] = _setupNextTick(internalTickCallback); - - // *Must* match Environment::TickInfo::Fields in src/env.h. - const kHasScheduled = 0; - const kHasPromiseRejections = 1; - - const queue = new FixedQueue(); - - process.nextTick = nextTick; - // Needs to be accessible from beyond this scope. - process._tickCallback = _tickCallback; - - function _tickCallback() { - if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0) - runMicrotasks(); - if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0) - 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; - } +const { + // For easy access to the nextTick state in the C++ land, + // and to avoid unnecessary calls into JS land. + tickInfo, + // Used to run V8's micro task queue. + runMicrotasks, + setTickCallback, + initializePromiseRejectCallback +} = internalBinding('task_queue'); + +const { + promiseRejectHandler, + emitPromiseRejectionWarnings +} = require('internal/process/promises'); + +const { + getDefaultTriggerAsyncId, + newAsyncId, + initHooksExist, + destroyHooksExist, + emitInit, + emitBefore, + emitAfter, + emitDestroy, + symbols: { async_id_symbol, trigger_async_id_symbol } +} = require('internal/async_hooks'); +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; +const kHasPromiseRejections = 1; + +const queue = new FixedQueue(); + +function runNextTicks() { + if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0) + runMicrotasks(); + if (tickInfo[kHasScheduled] === 0 && tickInfo[kHasPromiseRejections] === 0) + return; + + internalTickCallback(); +} - class TickObject { - constructor(callback, args, triggerAsyncId) { - // This must be set to null first to avoid function tracking - // on the hidden class, revisit in V8 versions after 6.2 - this.callback = null; - this.callback = callback; - this.args = args; - - const asyncId = newAsyncId(); - this[async_id_symbol] = asyncId; - this[trigger_async_id_symbol] = triggerAsyncId; - - if (initHooksExist()) { - emitInit(asyncId, - 'TickObject', - triggerAsyncId, - this); - } +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; +} - // `nextTick()` will not enqueue any callback when the process is about to - // exit since the callback would not have a chance to be executed. - 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]; +class TickObject { + constructor(callback, args, triggerAsyncId) { + // This must be set to null first to avoid function tracking + // on the hidden class, revisit in V8 versions after 6.2 + this.callback = null; + this.callback = callback; + this.args = args; + + const asyncId = newAsyncId(); + this[async_id_symbol] = asyncId; + this[trigger_async_id_symbol] = triggerAsyncId; + + if (initHooksExist()) { + emitInit(asyncId, + 'TickObject', + triggerAsyncId, + this); } + } +} - if (queue.isEmpty()) - tickInfo[kHasScheduled] = 1; - queue.push(new TickObject(callback, args, getDefaultTriggerAsyncId())); +// `nextTick()` will not enqueue any callback when the process is about to +// exit since the callback would not have a chance to be executed. +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 + }; +}; diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 31c96293b3..df4e8188fe 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -1,21 +1,16 @@ 'use strict'; const { safeToString } = internalBinding('util'); +const { + promiseRejectEvents +} = internalBinding('task_queue'); const maybeUnhandledPromises = new WeakMap(); const pendingUnhandledRejections = []; const asyncHandledRejections = []; -const promiseRejectEvents = {}; let lastPromiseId = 0; -exports.setup = setupPromises; - -function setupPromises(_setupPromises) { - _setupPromises(handler, promiseRejectEvents); - return emitPromiseRejectionWarnings; -} - -function handler(type, promise, reason) { +function promiseRejectHandler(type, promise, reason) { switch (type) { case promiseRejectEvents.kPromiseRejectWithNoHandler: return unhandledRejection(promise, reason); @@ -124,3 +119,8 @@ function emitPromiseRejectionWarnings() { } return maybeScheduledTicks || pendingUnhandledRejections.length !== 0; } + +module.exports = { + promiseRejectHandler, + emitPromiseRejectionWarnings +}; |