-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This reverts commit b9d65a4.
- Loading branch information
Showing
5 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
require("../spec_helper") | ||
|
||
_ = require("lodash") | ||
|
||
parent = require("#{root}timers/parent") | ||
|
||
describe "timers/parent", -> | ||
context ".fix", -> | ||
beforeEach -> | ||
parent.restore() | ||
@timer = parent.fix() | ||
|
||
describe "setTimeout", -> | ||
it "returns timer object", (done) -> | ||
obj = setTimeout(done, 10) | ||
|
||
expect(obj.id).to.eq(1) | ||
expect(obj.ref).to.be.a("function") | ||
expect(obj.unref).to.be.a("function") | ||
|
||
it "increments timer id", (done) -> | ||
fn = _.after(2, done) | ||
|
||
obj1 = setTimeout(fn, 10) | ||
obj2 = setTimeout(fn, 10) | ||
|
||
expect(obj2.id).to.eq(2) | ||
|
||
it "slices out of queue once cb is invoked", (done) -> | ||
fn = => | ||
expect(@timer.queue).to.deep.eq({}) | ||
done() | ||
|
||
setTimeout(fn, 10) | ||
|
||
expect(@timer.queue[1].cb).to.eq(fn) | ||
|
||
describe "clearTimeout", -> | ||
it "does not explode when passing null", -> | ||
clearTimeout(null) | ||
|
||
it "can clear the timeout and prevent the cb from being invoked", (done) -> | ||
fn = => | ||
done(new Error("should not have been invoked")) | ||
|
||
timer = setTimeout(fn, 10) | ||
|
||
expect(@timer.queue[1].cb).to.eq(fn) | ||
|
||
clearTimeout(timer) | ||
|
||
expect(@timer.queue).to.deep.eq({}) | ||
|
||
setTimeout -> | ||
done() | ||
, 20 | ||
|
||
describe "setInterval", -> | ||
it "returns timer object", (done) -> | ||
obj = setInterval -> | ||
clearInterval(obj) | ||
|
||
done() | ||
, 10 | ||
|
||
expect(obj.id).to.eq(1) | ||
expect(obj.ref).to.be.a("function") | ||
expect(obj.unref).to.be.a("function") | ||
|
||
it "increments timer id", (done) -> | ||
fn = _.after 2, -> | ||
clearInterval(obj1) | ||
clearInterval(obj2) | ||
done() | ||
|
||
obj1 = setInterval(fn, 10) | ||
obj2 = setInterval(fn, 10000) | ||
|
||
expect(obj2.id).to.eq(2) | ||
|
||
it "continuously polls until cleared", (done) -> | ||
poller = _.after 3, => | ||
clearInterval(t) | ||
|
||
setTimeout -> | ||
expect(fn).to.be.calledThrice | ||
done() | ||
, 100 | ||
|
||
fn = sinon.spy(poller) | ||
|
||
t = setInterval(fn, 10) | ||
|
||
describe "clearInterval", -> | ||
it "does not explode when passing null", -> | ||
clearInterval(null) | ||
|
||
it "can clear the interval and prevent the cb from being invoked", (done) -> | ||
fn = => | ||
done(new Error("should not have been invoked")) | ||
|
||
timer = setInterval(fn, 10) | ||
|
||
expect(@timer.queue[1].cb).to.exist | ||
|
||
clearInterval(timer) | ||
|
||
expect(@timer.queue).to.deep.eq({}) | ||
|
||
setTimeout -> | ||
done() | ||
, 20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const log = require('debug')('cypress:server:timers') | ||
|
||
process.on('message', (obj = {}) => { | ||
const { id, ms } = obj | ||
|
||
log('child received timer id %d', id) | ||
|
||
setTimeout(() => { | ||
try { | ||
log('child sending timer id %d', id) | ||
|
||
// process.send could throw if | ||
// parent process has already exited | ||
process.send({ | ||
id, | ||
ms, | ||
}) | ||
} catch (err) { | ||
// eslint-disable no-empty | ||
} | ||
}, ms) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// electron has completely busted timers resulting in | ||
// all kinds of bizarre timeouts and unresponsive UI | ||
// https://github.com/electron/electron/issues/7079 | ||
// | ||
// this fixes this problem by replacing all the global | ||
// timers and implementing a lightweight queuing mechanism | ||
// involving a forked process | ||
|
||
const cp = require('child_process') | ||
const path = require('path') | ||
const log = require('debug')('cypress:server:timers') | ||
|
||
const st = global.setTimeout | ||
const si = global.setInterval | ||
const ct = global.clearTimeout | ||
const ci = global.clearInterval | ||
|
||
let child = null | ||
|
||
function noop () {} | ||
|
||
function restore () { | ||
// restore | ||
global.setTimeout = st | ||
global.setInterval = si | ||
global.clearTimeout = ct | ||
global.clearInterval = ci | ||
|
||
if (child) { | ||
child.kill() | ||
} | ||
|
||
child = null | ||
} | ||
|
||
function fix () { | ||
const queue = {} | ||
let idCounter = 0 | ||
|
||
function sendAndQueue (id, cb, ms, args) { | ||
// const started = Date.now() | ||
log('queuing timer id %d after %d ms', id, ms) | ||
|
||
queue[id] = { | ||
// started, | ||
args, | ||
ms, | ||
cb, | ||
} | ||
|
||
child.send({ | ||
id, | ||
ms, | ||
}) | ||
|
||
// return the timer object | ||
return { | ||
id, | ||
ref: noop, | ||
unref: noop, | ||
} | ||
} | ||
|
||
function clear (id) { | ||
log('clearing timer id %d from queue %o', id, queue) | ||
|
||
delete queue[id] | ||
} | ||
|
||
// fork the child process | ||
let child = cp.fork(path.join(__dirname, 'child.js'), [], { | ||
stdio: 'inherit', | ||
}) | ||
.on('message', (obj = {}) => { | ||
const { id } = obj | ||
|
||
const msg = queue[id] | ||
|
||
// if we didn't get a msg | ||
// that means we must have | ||
// cleared the timeout already | ||
if (!msg) { | ||
return | ||
} | ||
|
||
const { cb, args } = msg | ||
|
||
clear(id) | ||
|
||
cb(...args) | ||
}) | ||
|
||
// In linux apparently the child process is never | ||
// exiting which causes cypress to hang indefinitely. | ||
// It would **SEEM** as if we... | ||
// 1. dont need to manually kill our child process | ||
// because it should naturally exit. | ||
// (but of course it doesn't in linux) | ||
// 2. use our restore function already defined above. | ||
// however when using the restore function above | ||
// the 'child' reference is null. how is it null? | ||
// it makes no sense. there must be a rip in the | ||
// space time continuum, obviously. that or the | ||
// child reference as the rest of the matter of | ||
// the universe has succumbed to entropy. | ||
process.on('exit', () => { | ||
child && child.kill() | ||
|
||
restore() | ||
}) | ||
|
||
global.setTimeout = function (cb, ms, ...args) { | ||
idCounter += 1 | ||
|
||
return sendAndQueue(idCounter, cb, ms, args) | ||
} | ||
|
||
global.clearTimeout = function (timer) { | ||
if (!timer) { | ||
return | ||
} | ||
|
||
// return undefined per the spec | ||
clear(timer.id) | ||
} | ||
|
||
global.clearInterval = function (timer) { | ||
if (!timer) { | ||
return | ||
} | ||
|
||
// return undefined per the spec | ||
clear(timer.id) | ||
} | ||
|
||
global.setInterval = function (fn, ms, ...args) { | ||
const permId = idCounter += 1 | ||
|
||
function cb () { | ||
// we want to immediately poll again | ||
// because our permId was just cleared | ||
// from the queue stack | ||
poll() | ||
|
||
fn() | ||
} | ||
|
||
function poll () { | ||
return sendAndQueue(permId, cb, ms, args) | ||
} | ||
|
||
return poll() | ||
} | ||
|
||
return { | ||
child, | ||
|
||
queue, | ||
} | ||
} | ||
|
||
module.exports = { | ||
restore, | ||
|
||
fix, | ||
} |