IE8 setInterval и setTimeout срабатывает сразу же после 49 дней безотказной работы

Поскольку система Windows приближается к 49,7 дням безотказной работы, внутренний счетчик тиков в миллисекундах Windows приближается к 2 ^ 32. Ошибка в Internet Explorer 8, похоже, имеет арифметическое переполнение при расчете, когда запускается событие setInterval или setTimeout. Например, если вы находитесь на 49-й день безотказной работы и вызываете

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

func будет вызываться немедленно, а не через 24 часа.

Эта ошибка, вероятно, произойдет в любое время после 25 дней безотказной работы (2 ^ 31 миллисекунды), если достаточно большое количество передано setInterval или setTimeout. (Я проверил только 49-й день.)

Вы можете проверить количество дней ожидания, введя "net statistics server" в командной строке.

Есть ли способ обхода?

Ответ 1

Вы можете обойти ошибку, используя обертку для 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.

Ответ 2

Этот элемент помог решить основную головную боль в приложении, над которым я работал, поэтому большое спасибо для его публикации. Утилита AdjustTickCount - это фантастический и необходимый инструмент для проверки решений, поэтому для этого также важны.

Проблема также влияет на IE 7, и она также влияет на IE 6, но с худшими последствиями, поскольку браузер перестает отвечать на все вместе, и решения, похоже, тоже не работают на этой версии. Есть еще много пользователей этих более старых версий, особенно в мире бизнеса/предприятия.

Я не обнаружил, что левая кнопка мыши была фактором в Windows XP, проблема возникает без нее.

Первые два ответа хороши, если задержка ожидания составляет всего несколько секунд, и в приложении установлено только очень небольшое количество тайм-аутов. Если требуется больше и больше тайм-аутов, тогда необходимо сделать больше работы, чтобы веб-приложение стало непригодным. В среде Web 2.0 RIA с использованием фреймворка, такого как qooxdoo пользователи могут оставить его в работе в течение нескольких дней, чтобы можно было большая потребность в более длинных тайм-аутах, чем пара полтора секундных задержек для создания анимации или другого кратковременного эффекта.

Первое решение - хорошее начало, но установка следующего тайм-аута на target-now снова вызовет функцию немедленно, потому что это все равно сделает задержку + время превышения 2 ^ 32 миллисекунды, и, следовательно, JS-код будет вращаться до тех пор, пока uptime обходит вокруг 0 ​​(или пользователь убивает браузер).

Второе решение является улучшением, поскольку преждевременные тайм-ауты будут возникать только с интервалом в 1 секунду, пока до этого момента не будет в пределах 1 секунды от времени всплеска, который позволяет другому коду получить внешний вид, но эксперименты показали, что все еще может быть достаточно для сделать браузер непригодным для использования, если в игре осталось достаточно ожидающих таймаутов. И он будет продолжаться до тех пор, пока время ожидания не обернется, поэтому, если запрошенная задержка достаточно длинная, пользователь все равно может решить убить браузер.

Менее процессорное голодное решение состоит в том, чтобы установить задержку для каждого последующего тайм-аута на половину продолжительности предыдущей задержки до тех пор, пока эта задержка не станет меньше 500 мс, и в это время мы знаем, что точка обертывания для времени безотказной работы неизбежна (< 1 секунда), и мы можем установить следующий тайм-аут на target-now, чтобы проверка преждевременного тайм-аута прекращалась только после небольшого количества последующих циклов. Сколько времени потребуется для достижения этой цели, будет зависеть от того, как долго была первоначальная задержка и насколько близок всплеск времени ожидания, когда был вызван setSafeTimeout, но в конечном итоге и при минимальной загрузке ЦП приложение возвращается к нормальному поведению без какого-либо длительного простоя пользователя.

Что-то вроде этого:

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 ^ 32 мс. В этом случае следующий интервал ожидания, используемый в вышеуказанной функции, может быть больше времени, оставшегося до первоначальной цели, что приводит к более длительному ожиданию, чем первоначально было необходимо.

Ниже приведена другая версия, которая разрешает эту проблему:

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

Ответ 3

Вариант ответа Камерона Джордана:

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 казалось связанным с нажатой левой кнопкой мыши, по этот пост.