diff --git a/src/node.cc b/src/node.cc index 76d6503366d..2223c369831 100644 --- a/src/node.cc +++ b/src/node.cc @@ -967,6 +967,10 @@ void SetupDomainUse(const FunctionCallbackInfo& args) { FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupDomainUse")); } +void RunMicrotasks(const FunctionCallbackInfo& args) { + args.GetIsolate()->RunMicrotasks(); +} + void SetupNextTick(const FunctionCallbackInfo& args) { HandleScope handle_scope(args.GetIsolate()); @@ -974,6 +978,7 @@ void SetupNextTick(const FunctionCallbackInfo& args) { assert(args[0]->IsObject()); assert(args[1]->IsFunction()); + assert(args[2]->IsObject()); // Values use to cross communicate with processNextTick. Local tick_info_obj = args[0].As(); @@ -984,6 +989,8 @@ void SetupNextTick(const FunctionCallbackInfo& args) { env->set_tick_callback_function(args[1].As()); + NODE_SET_METHOD(args[2].As(), "runMicrotasks", RunMicrotasks); + // Do a little housekeeping. env->process_object()->Delete( FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick")); diff --git a/src/node.js b/src/node.js index 65092249d55..e7b45ad5cc7 100644 --- a/src/node.js +++ b/src/node.js @@ -294,6 +294,10 @@ var _runAsyncQueue = tracing._runAsyncQueue; var _loadAsyncQueue = tracing._loadAsyncQueue; var _unloadAsyncQueue = tracing._unloadAsyncQueue; + var microtasksScheduled = false; + + // Used to run V8's micro task queue. + var _runMicrotasks = {}; // This tickInfo thing is used so that the C++ code in src/node.cc // can have easy accesss to our nextTick state, and avoid unnecessary @@ -312,7 +316,9 @@ process._tickCallback = _tickCallback; process._tickDomainCallback = _tickDomainCallback; - process._setupNextTick(tickInfo, _tickCallback); + process._setupNextTick(tickInfo, _tickCallback, _runMicrotasks); + + _runMicrotasks = _runMicrotasks.runMicrotasks; function tickDone() { if (tickInfo[kLength] !== 0) { @@ -327,11 +333,34 @@ tickInfo[kIndex] = 0; } + function scheduleMicrotasks() { + if (microtasksScheduled) + return; + + nextTickQueue.push({ + callback: runMicrotasksCallback, + domain: null + }); + + tickInfo[kLength]++; + microtasksScheduled = true; + } + + function runMicrotasksCallback() { + microtasksScheduled = false; + _runMicrotasks(); + + if (tickInfo[kIndex] < tickInfo[kLength]) + scheduleMicrotasks(); + } + // Run callbacks that have no domain. // Using domains will cause this to be overridden. function _tickCallback() { var callback, hasQueue, threw, tock; + scheduleMicrotasks(); + while (tickInfo[kIndex] < tickInfo[kLength]) { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; @@ -358,6 +387,8 @@ function _tickDomainCallback() { var callback, domain, hasQueue, threw, tock; + scheduleMicrotasks(); + while (tickInfo[kIndex] < tickInfo[kLength]) { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; diff --git a/test/simple/test-microtask-queue-integration-domain.js b/test/simple/test-microtask-queue-integration-domain.js new file mode 100644 index 00000000000..2197bf9212e --- /dev/null +++ b/test/simple/test-microtask-queue-integration-domain.js @@ -0,0 +1,70 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var domain = require('domain'); + +var implementations = [ + function (fn) { + Promise.resolve().then(fn); + }, + function (fn) { + var obj = {}; + + Object.observe(obj, fn); + + obj.a = 1; + } +]; + +var expected = 0; +var done = 0; + +process.on('exit', function () { + assert.equal(done, expected); +}); + +function test (scheduleMicrotask) { + var nextTickCalled = false; + expected++; + + scheduleMicrotask(function () { + process.nextTick(function () { + nextTickCalled = true; + }); + + setTimeout(function () { + assert(nextTickCalled); + done++; + }, 0); + }); +} + +// first tick case +implementations.forEach(test); + +// tick callback case +setTimeout(function () { + implementations.forEach(function (impl) { + process.nextTick(test.bind(null, impl)); + }); +}, 0); diff --git a/test/simple/test-microtask-queue-integration.js b/test/simple/test-microtask-queue-integration.js new file mode 100644 index 00000000000..af01548477f --- /dev/null +++ b/test/simple/test-microtask-queue-integration.js @@ -0,0 +1,69 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); + +var implementations = [ + function (fn) { + Promise.resolve().then(fn); + }, + function (fn) { + var obj = {}; + + Object.observe(obj, fn); + + obj.a = 1; + } +]; + +var expected = 0; +var done = 0; + +process.on('exit', function () { + assert.equal(done, expected); +}); + +function test (scheduleMicrotask) { + var nextTickCalled = false; + expected++; + + scheduleMicrotask(function () { + process.nextTick(function () { + nextTickCalled = true; + }); + + setTimeout(function () { + assert(nextTickCalled); + done++; + }, 0); + }); +} + +// first tick case +implementations.forEach(test); + +// tick callback case +setTimeout(function () { + implementations.forEach(function (impl) { + process.nextTick(test.bind(null, impl)); + }); +}, 0);