diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2019-03-08 16:28:19 +0100 |
---|---|---|
committer | Daniel Bevenius <daniel.bevenius@gmail.com> | 2019-03-11 06:11:12 +0100 |
commit | 8d669bbeb1ed77e9cdc679f75d9e8529f5764087 (patch) | |
tree | 2b84c3a090de762ab014cb39f359cfcd68b40e9e /lib/internal/process/task_queues.js | |
parent | cc2c07ce0859f2df626bce912ba456439fdb33ec (diff) | |
download | android-node-v8-8d669bbeb1ed77e9cdc679f75d9e8529f5764087.tar.gz android-node-v8-8d669bbeb1ed77e9cdc679f75d9e8529f5764087.tar.bz2 android-node-v8-8d669bbeb1ed77e9cdc679f75d9e8529f5764087.zip |
process: refactor global.queueMicrotask()
- Lazy load `async_hooks` in the implementation
- Rename `process/next_tick.js` to `process/task_queues.js`
and move the implementation of `global.queueMicrotask()`
there since these methods are conceptually related to
each other.
- Move the bindings used by `global.queueMicrotask()` into
`node_task_queue.cc` instead of the generic `node_util.cc`
- Use `defineOperation` to define `global.queueMicrotask()`
PR-URL: https://github.com/nodejs/node/pull/26523
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'lib/internal/process/task_queues.js')
-rw-r--r-- | lib/internal/process/task_queues.js | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/lib/internal/process/task_queues.js b/lib/internal/process/task_queues.js new file mode 100644 index 0000000000..9e4fba6d9d --- /dev/null +++ b/lib/internal/process/task_queues.js @@ -0,0 +1,186 @@ +'use strict'; + +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, + enqueueMicrotask, + triggerFatalException +} = internalBinding('task_queue'); + +const { + setHasRejectionToWarn, + hasRejectionToWarn, + listenForRejections, + processPromiseRejections +} = 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, + ERR_INVALID_ARG_TYPE +} = require('internal/errors').codes; +const FixedQueue = require('internal/fixed_queue'); + +// *Must* match Environment::TickInfo::Fields in src/env.h. +const kHasTickScheduled = 0; + +function hasTickScheduled() { + return tickInfo[kHasTickScheduled] === 1; +} +function setHasTickScheduled(value) { + tickInfo[kHasTickScheduled] = value ? 1 : 0; +} + +const queue = new FixedQueue(); + +function runNextTicks() { + if (!hasTickScheduled() && !hasRejectionToWarn()) + runMicrotasks(); + if (!hasTickScheduled() && !hasRejectionToWarn()) + return; + + processTicksAndRejections(); +} + +function processTicksAndRejections() { + 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); + } + setHasTickScheduled(false); + runMicrotasks(); + } while (!queue.isEmpty() || processPromiseRejections()); + setHasRejectionToWarn(false); +} + +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); + } + } +} + +// `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()) + setHasTickScheduled(true); + queue.push(new TickObject(callback, args, getDefaultTriggerAsyncId())); +} + +let AsyncResource; +function createMicrotaskResource() { + // Lazy load the async_hooks module + if (!AsyncResource) { + AsyncResource = require('async_hooks').AsyncResource; + } + return new AsyncResource('Microtask', { + triggerAsyncId: getDefaultTriggerAsyncId(), + requireManualDestroy: true, + }); +} + +function queueMicrotask(callback) { + if (typeof callback !== 'function') { + throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback); + } + + const asyncResource = createMicrotaskResource(); + + enqueueMicrotask(() => { + asyncResource.runInAsyncScope(() => { + try { + callback(); + } catch (error) { + // TODO(devsnek) remove this if + // https://bugs.chromium.org/p/v8/issues/detail?id=8326 + // is resolved such that V8 triggers the fatal exception + // handler for microtasks + triggerFatalException(error); + } finally { + asyncResource.emitDestroy(); + } + }); + }); +} + +module.exports = { + setupTaskQueue() { + // Sets the per-isolate promise rejection callback + listenForRejections(); + // Sets the callback to be run in every tick. + setTickCallback(processTicksAndRejections); + return { + nextTick, + runNextTicks + }; + }, + queueMicrotask +}; |