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();
});
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: {},

View File

@@ -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);

View File

@@ -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;
}
}