node/lib/internal/process/task_queues.js
Michaël Zasso 0817840f77 lib: force using primordials for JSON, Math and Reflect
Use the "no-restricted-globals" ESLint rule to lint for it.

PR-URL: https://github.com/nodejs/node/pull/27027
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2019-04-03 21:36:08 +08:00

189 lines
5.0 KiB
JavaScript

'use strict';
const { Reflect } = primordials;
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
};