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
This commit is contained in:
Gregg Van Hove
2017-06-15 14:21:33 -07:00
parent 4c491b2dc0
commit c60d669940
3 changed files with 94 additions and 14 deletions

View File

@@ -21,6 +21,30 @@ describe("ClearStack", function() {
expect(setImmediate).toHaveBeenCalled(); 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() { it("uses MessageChannels when available", function() {
var fakeChannel = { var fakeChannel = {
port1: {}, port1: {},
@@ -37,6 +61,37 @@ describe("ClearStack", function() {
expect(called).toBe(true); 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() { it("calls setTimeout when onmessage is called recursively", function() {
var fakeChannel = { var fakeChannel = {
port1: {}, port1: {},

View File

@@ -952,16 +952,15 @@ describe("Env integration", function() {
}); });
describe("with a mock clock", function() { describe("with a mock clock", function() {
var originalTimeout;
beforeEach(function() { beforeEach(function() {
originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; this.originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL;
this.realSetTimeout = window.setTimeout;
jasmine.clock().install(); jasmine.clock().install();
}); });
afterEach(function() { afterEach(function() {
jasmine.clock().uninstall(); 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) { 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) { it('should wait a custom interval before reporting async functions that fail to call done', function(done) {
var env = new jasmineUnderTest.Env(), 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() { reporter.jasmineDone.and.callFake(function() {
expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite beforeAll times out', [ expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite beforeAll times out', [
@@ -1109,6 +1109,11 @@ describe("Env integration", function() {
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000; jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000;
env.describe('suite', function() { env.describe('suite', function() {
env.afterAll(function() {
realSetTimeout(function() {
jasmine.clock().tick(10);
}, 100);
});
env.describe('beforeAll', function() { env.describe('beforeAll', function() {
env.beforeAll(function(innerDone) { env.beforeAll(function(innerDone) {
jasmine.clock().tick(5001); jasmine.clock().tick(5001);

View File

@@ -1,5 +1,7 @@
getJasmineRequireObj().clearStack = function(j$) { getJasmineRequireObj().clearStack = function(j$) {
function messageChannelImpl(global) { var maxInlineCallCount = 10;
function messageChannelImpl(global, setTimeout) {
var channel = new global.MessageChannel(), var channel = new global.MessageChannel(),
head = {}, head = {},
tail = head; tail = head;
@@ -22,25 +24,43 @@ getJasmineRequireObj().clearStack = function(j$) {
} }
}; };
var currentCallCount = 0;
return function clearStack(fn) { return function clearStack(fn) {
tail = tail.next = { task: fn }; currentCallCount++;
channel.port2.postMessage(0);
if (currentCallCount < maxInlineCallCount) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
} else {
setTimeout(fn);
}
}; };
} }
function getClearStack(global) { 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)) { if (j$.isFunction_(global.setImmediate)) {
var realSetImmediate = global.setImmediate; var realSetImmediate = global.setImmediate;
return function(fn) { return function(fn) {
realSetImmediate(fn); currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
realSetImmediate(fn);
} else {
currentCallCount = 0;
setTimeoutImpl(fn);
}
}; };
} else if (!j$.util.isUndefined(global.MessageChannel)) { } else if (!j$.util.isUndefined(global.MessageChannel)) {
return messageChannelImpl(global); return messageChannelImpl(global, setTimeoutImpl);
} else { } else {
var realSetTimeout = global.setTimeout; return setTimeoutImpl;
return function clearStack(fn) {
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
};
} }
} }