2010-12-04 44 views
10

Comme un système Windows approche de 49,7 jours de disponibilité, le compteur de millisecondes Windows interne approche 2^32. Un bogue dans Internet Explorer 8 semble avoir un dépassement arithmétique lors du calcul du moment où déclencher un événement setInterval ou setTimeout. Par exemple, si vous êtes le jour 49 de disponibilité, et appelezIE8 setInterval et setTimeout se déclenchent immédiatement après 49 jours de disponibilité

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

la fonc sera appelée immédiatement, et non pas dans les 24 heures.

Ce bug se produira probablement pour tout le temps après 25 jours le temps de fonctionnement (2^31 millisecondes) si un assez grand nombre est passé à setInterval ou setTimeout. (Je ne l'ai vérifié le jour 49, cependant.)

Vous pouvez vérifier le nombre de jours Uptime en entrant « serveur de statistiques net » sur la ligne de commande.

Y a-t-il une solution de contournement?

+0

Y at-il une question? Cherchez-vous une solution de contournement ou partagez-la avec le monde? –

+0

Sooo ... vous êtes en train de dire que faire un timeout pendant 24h après 25 jours de disponibilité du système sera un bug? Je me demande quand j'ai besoin d'un tel délai? –

+0

Ceci est plus documentant un bug dans IE8. Pour un exemple de situation où cela pourrait poser un problème, disons que vous avez un feu de minuteur au bout d'une heure et que vous redirigez un utilisateur vers une autre page lorsqu'il se déclenche. Lorsque vous êtes dans une heure de 2^32 millisecondes de disponibilité, la minuterie se déclenchera immédiatement et l'utilisateur ne pourra pas accéder à la page d'origine. Une fois que le temps de fonctionnement passe 2^32 millisecondes, tout recommence à fonctionner. Mais pour cette heure, la page sera brisée. – user281806

Répondre

5

Vous pouvez contourner le bug en utilisant une enveloppe pour 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); 
} 

Cela renvoie toujours la valeur de setTimeout si le bug ne se rencontre pas clearTimeout peut encore être utilisé. Si clearTimeout besoins à l'épreuve des balles ou vous avez besoin setInterval (et probablement clearInterval) vous auriez besoin de jeter plus de code au problème, mais le principal de vérifier le temps écoulé avant d'exécuter func cales.

+0

merci, votre exemple m'a fait démarrer – user281806

+0

Juste vu cela sur le remplacement du navigateur setTimeout: http://www.adequatelygood.com/2011/4/Replacing-setTimeout-Globally –

1

Une variante de la réponse de Cameron Jordan:

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

Le but de la fonction d'aide est de s'appeler une fois par seconde si IE8 est dans la condition de bogue.

Un utilitaire utile pour les tests est AdjustTickCount (Windows XP uniquement, cependant). Définir le nouveau compte de tick à 0xffff0000, par exemple, vous donnera 65 secondes de comportement bogué avant que le compteur de tiques ne survole. Toute minuterie réglée à, disons, 120 secondes, ne se déclenchera pas correctement. En outre, dans Windows XP, le comportement bug-setTimeout semblait lié au clic gauche sur le bouton de la souris, conformément à this post.

3

Cet article a aidé à résoudre un mal de tête majeur dans une application sur laquelle j'ai travaillé, donc un grand merci pour l'avoir posté. L'utilitaire AdjustTickCount est un outil fantastique et essentiel pour prouver des solutions, donc un coup de pouce pour cela aussi bien.

Le problème affecte également IE 7 et il semble également influer sur IE 6, mais avec des conséquences pires que le navigateur cesse de répondre tous ensemble et les solutions ne semblent pas travailler sur cette version soit. Il y a encore beaucoup d'utilisateurs de ces anciennes versions particulièrement dans le monde des affaires/entreprises.

Je n'ai pas trouvé que le bouton gauche de la souris était un facteur dans Windows XP, le problème se produit sans elle.

Les deux premières réponses sont correctes si le délai d'attente est de quelques secondes tout au plus et que seul un très petit nombre de délais d'attente est défini dans l'application. Si des délais d'attente plus longs et plus longs sont requis, il reste beaucoup à faire pour éviter que l'application Web devienne inutilisable. Dans un Web 2.0 RIA en utilisant un cadre tel que qooxdoo les utilisateurs peuvent le laisser fonctionner pendant des jours de sorte qu'il peut y avoir un plus grand besoin de délais plus longs et plus longs qu'un couple de demi-secondes pour produire une animation ou un autre bref effet. La première solution est un bon début mais régler le prochain délai à target-now provoquera à nouveau l'appel de la fonction immédiatement car cela fera encore plus de temps de disponibilité + délai dépassera 2^32 millisecondes et donc le code JS tournera jusqu'à la disponibilité enveloppe autour de 0 (ou l'utilisateur tue le navigateur).

La deuxième solution est une amélioration parce que les délais d'attente prématurés ne se produiront à 1 seconde d'intervalle jusqu'à ce que maintenant est à moins de 1 seconde de l'enveloppe de disponibilité autour qui permet un autre code pour obtenir un look en mais des expériences ont montré que peut encore suffira à rendre le navigateur inutilisable s'il y a suffisamment de délais d'attente en attente. Et il continuera encore jusqu'à ce que le temps de disponibilité se termine donc si le délai demandé est assez long, l'utilisateur peut décider de tuer le navigateur.

Une solution moins gourmande en CPU est de définir le délai pour chaque temporisation suivante à la moitié de la temporisation précédente jusqu'à ce que ce délai soit inférieur à 500 ms, moment auquel nous savons que le temps de disponibilité est imminent (< 1 seconde) et nous pouvons définir le délai d'attente suivant à target-now afin que la vérification du délai d'expiration prématurée cesse après seulement un petit nombre de cycles supplémentaires. Le temps nécessaire pour y parvenir dépendra de la durée du délai original et de la proximité du temps de fonctionnement lorsque setSafeTimeout a été appelé, mais avec un minimum de chargement du processeur, l'application retrouve un comportement normal sans ralentissement prolongé pour l'utilisateur.

Quelque chose comme ceci:

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

raffinement supplémentaire:

J'ai trouvé que setTimeout() peut parfois invoquer le rappel de quelques millisecondes plus tôt que prévu, même lorsque le temps de fonctionnement du système tiques ne sont pas près de 2^32 ms. Dans ce cas, l'intervalle d'attente suivant utilisé dans la fonction ci-dessus peut être supérieur au temps restant à la cible d'origine, ce qui entraîne une attente plus longue que celle initialement souhaitée.

Ci-dessous est une autre version qui résout ce problème:

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

Une belle solution. – user281806

+0

Merci, je voterais pour la question, mais en tant que nouveau titulaire de compte, je suis empêché de le faire pour le moment. –