2010-12-04 57 views
10

由於Windows系統接近49.7天的正常運行時間,內部Windows毫秒滴答計數器接近2^32。 在計算何時觸發setInterval或setTimeout事件時,Internet Explorer 8中的一個錯誤似乎有算術溢出。例如,如果您在正常運行時間的第49天,並且致電IE8 setInterval和setTimeout在正常運行49天后立即觸發

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

函數將立即調用,而不是在24小時內調用。

如果足夠大的數字傳遞給setInterval或setTimeout,則在25天正常運行時間(2^31毫秒)後,任何時候都可能發生此錯誤。 (儘管我只在第49天檢查過。)

您可以通過在命令行中輸入「net statistics server」來檢查正常運行的天數。

是否有解決方法?

+0

有沒有問題?你在尋找解決方法還是與世界分享? – 2010-12-04 00:45:01

+0

Sooo ...你基本上是說在系統正常運行25天之後做24小時超時會有問題嗎?我想知道我什麼時候需要這樣的超時? – 2010-12-04 01:03:02

+0

這更多地記錄了IE8中的一個錯誤。舉例來說,這可能是一個問題,比如你在一個小時後有一個定時器觸發,當用戶觸發時將用戶重定向到另一個頁面。當您在2^32毫秒的正常運行時間的一小時內,計時器將立即啓動,用戶將無法訪問原始頁面。一旦正常運行時間通過2^32毫秒,所有事情都會再次開始工作。但是那一個小時,頁面就會被打破。 – user281806 2010-12-04 01:21:39

回答

5

你可以解決這個bug通過使用包裝爲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); 
} 

這仍然返回從setTimeout所以如果錯誤是沒有遇到clearTimeout仍然可以使用的值。如果clearTimeout需要防彈,或者您需要setInterval(推測爲clearInterval),則需要在此問題上拋出更多代碼,但在執行func之前驗證足夠時間的原則已過。

1

卡梅倫喬丹的回答的變化:

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

輔助功能的目的是調用自身一次第二,如果IE8處於錯誤狀態。

一個用於測試的有用工具是AdjustTickCount(僅限於Windows XP)。例如,將新的滴答計數設置爲0xffff0000,會在滴答計數器翻轉之前爲您提供65秒的錯誤行爲。任何設定爲120秒的計時器都不會正常啓動。

此外,在Windows XP中,setTimeout的錯誤行爲似乎與被點擊的鼠標左鍵相關,按照this post

3

這個項目已經幫助解決了我一直在努力的一個應用程序中的一個大難題,所以感謝發佈它。 AdjustTickCount實用程序是驗證解決方案的絕佳必備工具,因此也贊同此舉。

該問題也影響IE 7,它也似乎影響IE 6,但隨着瀏覽器停止全部響應並且該解決方案似乎無法在該版本上運行,結果會更糟。這些舊版本的用戶仍然很多,特別是在商業/企業領域。

我沒有發現鼠標左鍵是Windows XP中的一個因素,沒有它就會出現問題。

如果超時延遲最重要的是秒數,並且應用程序中設置的超時時間很少,前兩個答案都可以。如果需要更多和更長的超時時間,則需要做更多工作來防止Web應用程序無法使用。在Web 2中。0使用諸如qooxdoo之類的框架的RIA可能使其運行數天,因此可能需要更多和更長的超時而不是半秒的延遲來產生動畫或其他短暫效果。

第一種解決方案是一個好的開始,但將下一個超時設置爲target-now將再次導致該函數被立即調用,因爲它仍然會使正常運行時間+延遲超過2^32毫秒,因此JS代碼將旋轉,直到正常運行時間包裝到0(或用戶殺死瀏覽器)。

第二種解決方案是一種改進,因爲直到現在是正常運行時間包1秒左右,它允許其他代碼,來看看,在之內,但實驗表明,仍然可以過早超時將僅在1秒的間隔發生如果有足夠的暫停超時播放,足以使瀏覽器無法使用。它會一直持續到正常運行時間結束,所以如果請求的延遲時間足夠長,用戶仍然可能決定終止瀏覽器。

較少耗費CPU的解決方案是將每個後續超時的延遲設置爲前一延遲的一半,直到該延遲小於500毫秒,此時我們知道即將發生正常運行的環繞點(< 1秒),並且我們可以將下一個超時設置爲target-now,以便過早超時檢查在僅僅少量的其他週期後停止。達到這一程度需要多長時間取決於原始延遲的時間長短以及當調用setSafeTimeout時的正常運行時間週期有多接近,但最終以最小的CPU負載,應用程序恢復正常行爲而用戶沒有經歷任何長時間的減速。

事情是這樣的:

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

進一步細化:

我發現setTimeout()有時可以調用回調早幾毫秒甚至當系統正常運行時間滴答比預期不是接近2 ^爲32ms。在這種情況下,上述函數中使用的下一個等待時間間隔可能大於原始目標的剩餘時間,這會導致等待時間超過最初期望的時間。

下面是另一個版本,從而解決了這個問題:

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