Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IE8 setInterval and setTimeout fires immediately after 49 days of uptime

As a Windows system nears 49.7 days of uptime, the internal Windows millisecond tick counter approaches 2^32. A bug in Internet Explorer 8 seems to have an arithmetic overflow when calculating when to fire a setInterval or setTimeout event. For example, if you are on day 49 of uptime, and call

setInterval(func, 86400000); // fire event in 24 hours

the func will be called immediately, not in 24 hours.

This bug will probably occur for any time after 25 days uptime (2^31 milliseconds) if a large enough number is passed to setInterval or setTimeout. (I've only checked on day 49, though.)

You can check the number of days uptime by entering "net statistics server" on the command line.

Is there a workaround?

like image 515
user281806 Avatar asked Dec 04 '10 00:12

user281806


People also ask

What's the difference between setTimeout () and setInterval ()?

setTimeout() calls a passed-in function once after a specified delay, while setInterval() invokes one continuously at a designated time. As JavaScript is a singly-threaded language, both methods allow your code to run asynchronously.

Which is better setTimeout or setInterval?

setTimeout allows us to run a function once after the interval of time. setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.

What is the default time delay if you omit the delay parameter from setTimeout () or setInterval ()?

The delay is set in milliseconds and 1,000 milliseconds equals 1 second. If the delay is omitted from the setTimeout() method, then the delay is set to 0 and the function will execute.


2 Answers

You could work around the bug by using a wrapper for setTimeout

function setSafeTimeout(func, delay){
    var target = +new Date + delay;
    return setTimeout(function(){
        var now = +new Date;
        if(now < target) {
            setSafeTimeout(func, target - now);
        } else {
            func();
        }
    }, delay);
}

This still returns the value from setTimeout so if the bug is not encountered clearTimeout can still be used. If clearTimeout needs to be bulletproof or you need setInterval (and presumably clearInterval) you'd need to throw more code at the problem but the principal of verifying enough time has elapsed before executing func holds.

like image 101
Cameron Jordan Avatar answered Sep 28 '22 09:09

Cameron Jordan


This item has helped solve a major headache in an application I've been working on so a big thanks for posting it. The AdjustTickCount utility is a fantastic and essential tool for proving solutions so a thumbs up for that as well.

The problem also affects IE 7 and it also appears to affect IE 6 but with worse consequences as the browser stops responding all together and the solutions don't seem to work on that version either. There are still many users of these older versions particularly in the world of business/enterprise.

I did not find that the left mouse button was a factor in Windows XP, the problem occurs without it.

The first two answers are fine if the timeout delay is a matter of seconds at the very most and there are only a very small number of timeouts set in the application. If more and longer timeouts are required then there is more work to do to prevent the web app becoming unusable. In a Web 2.0 RIA using a framework such as qooxdoo users may leave it running for days so there can be greater need for more and longer timeouts than a couple of half second delays to produce animation or other brief effect.

The first solution is a good start but setting the next timeout to target-now will again cause the function to be called immediately because that will still make uptime+delay exceed 2^32 milliseconds and hence the JS code will spin until the uptime wraps around to 0 (or the user kills the browser).

The second solution is an improvement because the premature timeouts will only occur at 1 second intervals until now is within 1 second of the uptime wrap around which allows other code to get a look-in but experiments showed that can still be enough to make the browser unusable if there are enough pending timeouts in play. And it will still continue until the uptime wraps around so if the requested delay is long enough the user may still decide to kill the browser.

A less CPU hungry solution is to set the delay for each subsequent timeout to half the duration of the previous delay until that delay would be less than 500ms at which time we know the wrap around point for the uptime is imminent (< 1 second away) and we can set the next timeout to target-now so that the premature timeout checking ceases after only a small number of further cycles. How long it takes to reach this will depend on how long the original delay was and how close the uptime wrap around was when setSafeTimeout was called but eventually and with minimal CPU loading the application returns to normal behaviour without the user experiencing any prolonged slowdown.

Something like this:

function setSafeTimeout(func, delay) {
  var target = +new Date + delay;
  var newDelay = delay;
  var helper = function()
  {
    var now = +new Date;
    if (now < target)
    {
      newDelay /= 2; // halve the wait time and try again
      if(newDelay < 500) // uptime wrap around is imminent
      {
        newDelay = target-now; // go back to using original target
      }
      var handle = setTimeout(helper, newDelay);
      // if required record handle somewhere for clearTimeout
    }
    else
    {
      func();
    }
  };
  return setTimeout(helper, delay);
};

Further refinement:

I have found that setTimeout() can sometimes invoke the callback a few milliseconds earlier than expected even when the system uptime ticks is not close to 2^32ms. In this case the next wait interval used in the above function can be greater than the time remaining to the original target which results in a longer wait than originally desired.

Below is another version which resolves this issue:

function setSafeTimeout(func, delay) {
  var target = +new Date + delay;
  var newDelay = delay;
  var helper = function()
  {
    var now = +new Date;
    if (now < target)
    {
      var timeToTarget = target-now;
      newDelay /= 2; // halve the wait time and try again
      if(newDelay < 500 || newDelay > timeToTarget) // uptime wrap around is imminent
      {
        newDelay = timeToTarget; // go back to using original target
      }
      var handle = setTimeout(helper, newDelay);
      // if required record handle somewhere for clearTimeout
    }
    else
    {
      func();
    }
  };
  return setTimeout(helper, delay);
};
like image 24
Will P Avatar answered Sep 28 '22 10:09

Will P