开发者

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?


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.


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


A variation on Cameron Jordan's answer:

function setSafeTimeout(func, delay) {
    var target = +new Date + delay;
    var helper = function() {
        var now = +new Date;
            if (now < target) {
                setTimeout(arguments.callee, 1000);
            } else {
                func();
            }
        }
    return setTimeout(helper, delay);
}

The purpose of the helper function is to invoke itself once a second if IE8 is in the bug condition.

A useful utility for testing is AdjustTickCount (Windows XP only, though). Setting the New tick count to 0xffff0000, for example, will give you 65 seconds of buggy behavior before the tick counter rolls over. Any timer set to, say, 120 seconds, will not fire properly.

Also, in Windows XP, the buggy setTimeout behavior seemed tied to the left mouse button being clicked, as per this post.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜