diff --git a/benchmark/process/next-tick-depth-args.js b/benchmark/process/next-tick-depth-args.js index 066b4837856f60..06bea4676b3615 100644 --- a/benchmark/process/next-tick-depth-args.js +++ b/benchmark/process/next-tick-depth-args.js @@ -2,7 +2,7 @@ var common = require('../common.js'); var bench = common.createBenchmark(main, { - millions: [2] + millions: [12] }); process.maxTickDepth = Infinity; diff --git a/benchmark/process/next-tick-depth.js b/benchmark/process/next-tick-depth.js index bc513d338d6d8c..824b7a8b69df86 100644 --- a/benchmark/process/next-tick-depth.js +++ b/benchmark/process/next-tick-depth.js @@ -1,7 +1,7 @@ 'use strict'; var common = require('../common.js'); var bench = common.createBenchmark(main, { - millions: [2] + millions: [12] }); process.maxTickDepth = Infinity; diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index bd820abe9db6b5..e41064104e0c3e 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -10,6 +10,42 @@ exports.setup = setupNextTick; // Will be overwritten when setupNextTick() is called. exports.nextTick = null; +class NextTickQueue { + constructor() { + this.head = null; + this.tail = null; + this.length = 0; + } + + push(v) { + const entry = { data: v, next: null }; + if (this.length > 0) + this.tail.next = entry; + else + this.head = entry; + this.tail = entry; + ++this.length; + } + + shift() { + if (this.length === 0) + return; + const ret = this.head.data; + if (this.length === 1) + this.head = this.tail = null; + else + this.head = this.head.next; + --this.length; + return ret; + } + + clear() { + this.head = null; + this.tail = null; + this.length = 0; + } +} + function setupNextTick() { const async_wrap = process.binding('async_wrap'); const async_hooks = require('async_hooks'); @@ -27,7 +63,7 @@ function setupNextTick() { const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr, kInitTriggerId } = async_wrap.constants; const { async_id_symbol, trigger_id_symbol } = async_wrap; - var nextTickQueue = []; + var nextTickQueue = new NextTickQueue(); var microtasksScheduled = false; // Used to run V8's micro task queue. @@ -55,27 +91,29 @@ function setupNextTick() { function tickDone() { if (tickInfo[kLength] !== 0) { if (tickInfo[kLength] <= tickInfo[kIndex]) { - nextTickQueue = []; + nextTickQueue.clear(); tickInfo[kLength] = 0; } else { - nextTickQueue.splice(0, tickInfo[kIndex]); tickInfo[kLength] = nextTickQueue.length; } } tickInfo[kIndex] = 0; } + const microTasksTickObject = { + callback: runMicrotasksCallback, + args: undefined, + domain: null, + [async_id_symbol]: 0, + [trigger_id_symbol]: 0 + }; function scheduleMicrotasks() { if (microtasksScheduled) return; - const tickObject = - new TickObject(runMicrotasksCallback, undefined, null); // For the moment all microtasks come from the void until the PromiseHook // API is implemented. - tickObject[async_id_symbol] = 0; - tickObject[trigger_id_symbol] = 0; - nextTickQueue.push(tickObject); + nextTickQueue.push(microTasksTickObject); tickInfo[kLength]++; microtasksScheduled = true; @@ -86,8 +124,9 @@ function setupNextTick() { _runMicrotasks(); if (tickInfo[kIndex] < tickInfo[kLength] || - emitPendingUnhandledRejections()) + emitPendingUnhandledRejections()) { scheduleMicrotasks(); + } } function _combinedTickCallback(args, callback) { @@ -133,7 +172,8 @@ function setupNextTick() { function _tickCallback() { do { while (tickInfo[kIndex] < tickInfo[kLength]) { - const tock = nextTickQueue[tickInfo[kIndex]++]; + ++tickInfo[kIndex]; + const tock = nextTickQueue.shift(); const callback = tock.callback; const args = tock.args; @@ -174,7 +214,8 @@ function setupNextTick() { function _tickDomainCallback() { do { while (tickInfo[kIndex] < tickInfo[kLength]) { - const tock = nextTickQueue[tickInfo[kIndex]++]; + ++tickInfo[kIndex]; + const tock = nextTickQueue.shift(); const callback = tock.callback; const domain = tock.domain; const args = tock.args; @@ -210,45 +251,48 @@ function setupNextTick() { } while (tickInfo[kLength] !== 0); } - function TickObject(callback, args, domain) { - this.callback = callback; - this.domain = domain; - this.args = args; - this[async_id_symbol] = -1; - this[trigger_id_symbol] = -1; - } - - function setupInit(tickObject, triggerAsyncId) { - tickObject[async_id_symbol] = ++async_uid_fields[kAsyncUidCntr]; - tickObject[trigger_id_symbol] = triggerAsyncId || initTriggerId(); - if (async_hook_fields[kInit] > 0) { - emitInit(tickObject[async_id_symbol], - 'TickObject', - tickObject[trigger_id_symbol], - tickObject); + class TickObject { + constructor(callback, args, asyncId, triggerAsyncId) { + this.callback = callback; + this.args = args; + this.domain = process.domain || null; + this[async_id_symbol] = asyncId; + this[trigger_id_symbol] = triggerAsyncId; } } + // `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 errors.TypeError('ERR_INVALID_CALLBACK'); - // on the way out, don't bother. it won't get fired anyway. + if (process._exiting) return; var args; - if (arguments.length > 1) { - args = new Array(arguments.length - 1); - for (var i = 1; i < arguments.length; i++) - args[i - 1] = arguments[i]; + 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]; } - var obj = new TickObject(callback, args, process.domain || null); - setupInit(obj, null); + const asyncId = ++async_uid_fields[kAsyncUidCntr]; + const triggerAsyncId = initTriggerId(); + const obj = new TickObject(callback, args, asyncId, triggerAsyncId); nextTickQueue.push(obj); - tickInfo[kLength]++; + ++tickInfo[kLength]; + if (async_hook_fields[kInit] > 0) + emitInit(asyncId, 'TickObject', triggerAsyncId, obj); } + // `internalNextTick()` will not enqueue any callback when the process is + // about to exit since the callback would not have a chance to be executed. function internalNextTick(triggerAsyncId, callback) { if (typeof callback !== 'function') throw new TypeError('callback is not a function'); @@ -259,17 +303,25 @@ function setupNextTick() { return; var args; - if (arguments.length > 2) { - args = new Array(arguments.length - 2); - for (var i = 2; i < arguments.length; i++) - args[i - 2] = arguments[i]; + switch (arguments.length) { + case 2: break; + case 3: args = [arguments[2]]; break; + case 4: args = [arguments[2], arguments[3]]; break; + case 5: args = [arguments[2], arguments[3], arguments[4]]; break; + default: + args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) + args[i - 2] = arguments[i]; } - var obj = new TickObject(callback, args, process.domain || null); - setupInit(obj, triggerAsyncId); + const asyncId = ++async_uid_fields[kAsyncUidCntr]; + const obj = new TickObject(callback, args, asyncId, triggerAsyncId); + nextTickQueue.push(obj); + ++tickInfo[kLength]; + if (async_hook_fields[kInit] > 0) + emitInit(asyncId, 'TickObject', triggerAsyncId, obj); + // The call to initTriggerId() was skipped, so clear kInitTriggerId. async_uid_fields[kInitTriggerId] = 0; - nextTickQueue.push(obj); - tickInfo[kLength]++; } }