From c60d669940a1daf19a8f74f5493bae257fda2f15 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Thu, 15 Jun 2017 14:21:33 -0700 Subject: [PATCH] Break into a `setTimeout` every once in a while - Allows the CPU to run other things that used the real `setTimeout` - Fixes #1327 - See #1334 - Fixes jasmine/gulp-jasmine-browser#48 --- spec/core/ClearStackSpec.js | 55 ++++++++++++++++++++++++++++++++ spec/core/integration/EnvSpec.js | 15 ++++++--- src/core/ClearStack.js | 38 ++++++++++++++++------ 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/spec/core/ClearStackSpec.js b/spec/core/ClearStackSpec.js index a43370ad..2ea889d3 100644 --- a/spec/core/ClearStackSpec.js +++ b/spec/core/ClearStackSpec.js @@ -21,6 +21,30 @@ describe("ClearStack", function() { expect(setImmediate).toHaveBeenCalled(); }); + it("uses setTimeout instead of setImmediate every 10 calls to make sure we release the CPU", function() { + var setImmediate = jasmine.createSpy('setImmediate'), + setTimeout = jasmine.createSpy('setTimeout'), + global = { setImmediate: setImmediate, setTimeout: setTimeout }, + clearStack = jasmineUnderTest.getClearStack(global); + + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + + expect(setImmediate).toHaveBeenCalled(); + expect(setTimeout).not.toHaveBeenCalled(); + + clearStack(function() { }); + expect(setImmediate.calls.count()).toEqual(9); + expect(setTimeout).toHaveBeenCalled(); + }); + it("uses MessageChannels when available", function() { var fakeChannel = { port1: {}, @@ -37,6 +61,37 @@ describe("ClearStack", function() { expect(called).toBe(true); }); + it("uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU", function() { + var fakeChannel = { + port1: {}, + port2: { + postMessage: jasmine.createSpy('postMessage').and.callFake(function() { + fakeChannel.port1.onmessage(); + }) + } + }, + setTimeout = jasmine.createSpy('setTimeout'), + global = { MessageChannel: function() { return fakeChannel; }, setTimeout: setTimeout }, + clearStack = jasmineUnderTest.getClearStack(global); + + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + clearStack(function() { }); + + expect(fakeChannel.port2.postMessage).toHaveBeenCalled(); + expect(setTimeout).not.toHaveBeenCalled(); + + clearStack(function() { }); + expect(fakeChannel.port2.postMessage.calls.count()).toEqual(9); + expect(setTimeout).toHaveBeenCalled(); + }); + it("calls setTimeout when onmessage is called recursively", function() { var fakeChannel = { port1: {}, diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index bb887c07..9b6927aa 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -952,16 +952,15 @@ describe("Env integration", function() { }); describe("with a mock clock", function() { - var originalTimeout; - beforeEach(function() { - originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; + this.originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; + this.realSetTimeout = window.setTimeout; jasmine.clock().install(); }); afterEach(function() { jasmine.clock().uninstall(); - jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.originalTimeout; }); it("should wait a specified interval before failing specs haven't called done yet", function(done) { @@ -1079,7 +1078,8 @@ describe("Env integration", function() { it('should wait a custom interval before reporting async functions that fail to call done', function(done) { var env = new jasmineUnderTest.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']), + realSetTimeout = this.realSetTimeout; reporter.jasmineDone.and.callFake(function() { expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite beforeAll times out', [ @@ -1109,6 +1109,11 @@ describe("Env integration", function() { jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000; env.describe('suite', function() { + env.afterAll(function() { + realSetTimeout(function() { + jasmine.clock().tick(10); + }, 100); + }); env.describe('beforeAll', function() { env.beforeAll(function(innerDone) { jasmine.clock().tick(5001); diff --git a/src/core/ClearStack.js b/src/core/ClearStack.js index 5a63a0ac..aeb42b34 100644 --- a/src/core/ClearStack.js +++ b/src/core/ClearStack.js @@ -1,5 +1,7 @@ getJasmineRequireObj().clearStack = function(j$) { - function messageChannelImpl(global) { + var maxInlineCallCount = 10; + + function messageChannelImpl(global, setTimeout) { var channel = new global.MessageChannel(), head = {}, tail = head; @@ -22,25 +24,43 @@ getJasmineRequireObj().clearStack = function(j$) { } }; + var currentCallCount = 0; return function clearStack(fn) { - tail = tail.next = { task: fn }; - channel.port2.postMessage(0); + currentCallCount++; + + if (currentCallCount < maxInlineCallCount) { + tail = tail.next = { task: fn }; + channel.port2.postMessage(0); + } else { + setTimeout(fn); + } }; } function getClearStack(global) { + var currentCallCount = 0; + var realSetTimeout = global.setTimeout; + var setTimeoutImpl = function clearStack(fn) { + Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); + }; + if (j$.isFunction_(global.setImmediate)) { var realSetImmediate = global.setImmediate; return function(fn) { - realSetImmediate(fn); + currentCallCount++; + + if (currentCallCount < maxInlineCallCount) { + realSetImmediate(fn); + } else { + currentCallCount = 0; + + setTimeoutImpl(fn); + } }; } else if (!j$.util.isUndefined(global.MessageChannel)) { - return messageChannelImpl(global); + return messageChannelImpl(global, setTimeoutImpl); } else { - var realSetTimeout = global.setTimeout; - return function clearStack(fn) { - Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); - }; + return setTimeoutImpl; } }