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?
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.
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.
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.
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);
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With