Created
August 1, 2017 05:06
-
-
Save brian-mann/94429cba31ec3a17b08649072bf15578 to your computer and use it in GitHub Desktop.
Fixed timers in electron
This file contains hidden or 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
process.on('message', (obj = {}) => { | |
const { id, ms } = obj | |
setTimeout(() => { | |
try { | |
// process.send could throw if | |
// parent process has already exited | |
process.send({ | |
id, | |
ms, | |
}) | |
} catch (err) { | |
// eslint-disable no-empty | |
} | |
}, ms) | |
}) |
This file contains hidden or 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
// 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', | |
env: { | |
ELECTRON_RUN_AS_NODE: true, | |
}, | |
}) | |
.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) | |
}) | |
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, | |
} |
This file contains hidden or 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
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 = @sandbox.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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment