electron: setTimeout not working reliably

  • Electron version: 1.3.5
  • Operating system: OS X

This is going to sound ridiculous, but setTimeout is not working for me in the main process. Consider this trivial application, no windows or anything:

var Electron = require( 'electron' );
var app = Electron.app;  // Module to control application life.

// This method will be called when Electron has done everything
// initialization and ready for creating browser windows.
app.on('ready', function() {

  var x = 0;
  var timeout_tester = function run_again(){

    console.log( x, x, x );
    x += 1;
    setTimeout( run_again, 30 );
  };

  timeout_tester();
});

When I run this, I see the counter increment on the console until ~900. From then on it very sporadically fires. I cannot tell you how long it took me to track an issue I was having down to this, and even then I did not believe it. Please tell me someone can reproduce this or point out the wildly stupid thing I am doing.

Thanks in advance

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 11
  • Comments: 39 (15 by maintainers)

Commits related to this issue

Most upvoted comments

The problem isn’t milliseconds. It’s off by orders of magnitude. Something that’s supposed to respond in 500ms can take 5-10 seconds sometimes.

On Mar 10, 2017, at 3:48 PM, Brad Metcalf notifications@github.com wrote:

setTimer has never been accurate and never will be by design. It is just usually just good enough. So I am unsure if this is something that can be fixed. Maybe improved but never fixed. An alternative is using a self adjusting timer. There are guides and libraries to help achieve this. But again, cannot be 100% accurate especially if you are dealing with milliseconds.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

Just pass the command line argument “–disable-background-timer-throttling” (see https://peter.sh/experiments/chromium-command-line-switches/)

For anyone still having this issue, I realized that VSCode was adding in

import { setTimeout } from 'timers';

automatically and causing this issue. I removed that and problem solved.

We had a simple multi-timer app using setInterval and noticed it was significantly slow when the tab was minimized or not in focus. Adding mainWindow.webContents.setBackgroundThrottling(false); seemed to have fixed it.

@tarruda do you think this is worth creating a tutorial doc for?

I don’t think it would be ideal to encourage users to do this.

Not sure if the child_process.fork would work as I haven’t tested. As @dregenor commented, in our project we use a simple C++ program to wake the main process with stdout messages(similar to @brianmmorton solution, but cross platform). We chose to do this because a new electron process would consume a lot more memory, and our requirements are simple.

I’m still not 100% sure this is a chromium bug, I assume it is because it seems to affect setTimeout of plain chrome renderers(if the tab is in background). If there’s no hope of fixing this in the short term with a libchromiumcontent patch, maybe we could use a similar approach as a short term fix by replacing electron’s setTimeout/setInterval with versions that use the hack on behalf of the users, and if/when we manage to fix in chromium, revert it.

Timer in renderer process is not working reliably , too.

contents.setBackgroundThrottling(allowed) allowed Boolean

Controls whether or not this WebContents will throttle animations and timers when the page becomes backgrounded. This also affects the Page Visibility API.

An important thing to remember here is that setTimeout is NOT guarunteed to fire exactly the provided number of milliseconds after calling.

See this section of the MSDN docs for more information https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#Reasons_for_delays_longer_than_specified

In particular this bit

In addition to “clamping”, the timeout can also fire later when the page (or the OS/browser itself) is busy with other tasks.

Even in Chrome and firefox my setTimeout calls are a few ms most of the time.

That being said, this does seem like quite a large jump from “just a few ms”. Perhaps Electron’s queue is generally a lot busier 😕

Here’s a gist with our code copied / pasted with the fixes for setTimeouts.

https://gist.github.com/brian-mann/94429cba31ec3a17b08649072bf15578

I’ll be happy to point users directly to how it’s integrated into Cypress once we open source in the next week or two.

This works by spawning a forked process and using ipc with the child process (which will be spawned in node, not electron) and is not susceptible to busted timers.

You then override all of the global timers (first thing in your code) and create a lightweight queuing mechanism that reimplements setTimeout, clearTimeout, setInterval, and clearInterval.

We disable node-integration so this was only a problem outside the renderer process for us. It sounds more complicated than it really was to setup.

I can confirm that this 100% fixes the problem.

Here is a potential solution that works for my use case if anyone sees it fit:

Create a bash script, timer.sh

#!/usr/bin/env bash

while :; do date +%s; sleep $1; done;

Most likely you’ll want to sleep $1; at the start of the process too

Execute that using spawn and take in the stdout to use as the timer

const child = spawn('bash', [__dirname + '/timer.sh', TIMEOUT_DURATION]);
child.on('exit', () => {
  console.log('process exit');
});

child.stdout.pipe(process.stdout)
child.stderr.pipe(process.stderr)