Подтвердить что ты не робот

Обходной путь для IE10 setInterval Memory Leak

Во время тестирования нашей библиотеки Javascript я считаю, что мы обнаружили серьезную утечку памяти в IE10 (v10.0.9200.16519 - Windows 8 64 бит). Реализация Javascript setInterval.

Простой тестовый пример показал, что если переменная фиксируется в закрытии передаваемой функции как аргумент для последующего выполнения, она, похоже, никогда не становится подходящей для сбора мусора, то есть браузер, похоже, содержит ссылку на функции или, по крайней мере, замыкающих переменных.

Наш тестовый экземпляр выполняет функцию setInterval только один раз, а затем очищает интервал таймера, то есть через некоторое время никакой код не запускается больше, и никакие переменные не доступны больше (насколько я могу видеть, что в этом коде не отображаются глобальные переменные, за исключением метода для запуска в onload), тем не менее процесс занимает половину гигабайта памяти (в зависимости от количества итераций).

Интересно, что этого не происходит, если вместо этого использовать метод setTimeout (а также проблема не существует в IE9 и текущих версиях Chrome, FF).

Проблему можно увидеть с помощью этой скрипты.

Запустите его в новом экземпляре IE10 в Windows 8 и откройте диспетчер задач, чтобы посмотреть использование памяти. Он быстро вырастет до 350 мегабайт и останется там после выполнения script.

Это важная часть проблемного кода:

// the function that when called multiple times will cause the leak in IE10
var eatMemory = function() {
    var a = null; // the captured closure variable
    var intervalId = setInterval(function() {
       a = createBigArray(); // call a method that allocates a lot of memory
       clearInterval(intervalId); // stop the interval timer
    }, 100);
}

(Я знаю, что легко установить этот конкретный фрагмент кода, но это не главное - это всего лишь крошечный фрагмент кода, с которым мы столкнулись, что воспроизводит проблему. Реальный код фактически захватывает this в закрытие и этот объект никогда не собирают мусор.)

Есть ли ошибка в нашем коде или есть способ использовать setInterval, где переменная закрытия содержит ссылку на большой объект без запуска утечки памяти и без возврата к "рекурсивным" вызовам setTimeout?

(Я также разместил вопрос в MSDN)

Обновление: Эта проблема также существует в IE10 в Windows 7, но не существует, если вы переключаетесь в режим стандартов IE9. Я отправил это в MS Connect и сообщит о достигнутом прогрессе.

Обновление: Microsoft приняла проблему и сообщила, что она исправлена ​​в IE11 (версия для предварительного просмотра). Я сам это подтвердил (кто-нибудь?)

Обновление: IE 11 официально выпущен, и я больше не могу воспроизвести проблему с этой версией с моей системой (Win 8.1 Pro 64bit).

4b9b3361

Ответ 1

Для полноты я добавляю возможное обходное решение здесь:

Как я уже писал (и высказанные комментаторы), это можно обойти (не зафиксировать), возвращаясь к setTimeout. Это не является тривиальным, так как требуется некоторое ведение бухгалтерского учета. Вот мое рекомендуемое исправление, что вы можете проверить и использовать эту скрипту:

var registerSetIntervalFix = function(){
    var _setTimeout = window.setTimeout;
    var _clearTimeout = window.clearTimeout;
    window.setInterval = function(fn, interval){
        var recurse = function(){
            var newId = _setTimeout(recurse, interval);
            window.setInterval.mapping[returnValue] = newId;
            fn();
        }
        var id = _setTimeout(recurse, interval);
        var returnValue = id;
        while (window.setInterval.mapping[returnValue]){
            returnValue++;
        }
        window.setInterval.mapping[returnValue] = id;
        return returnValue;
    }
    window.setInterval.mapping = {};
    window.clearInterval = function(id){
        var realId = window.setInterval.mapping[id];
        _clearTimeout(realId);
        delete window.setInterval.mapping[id];
    }
}

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

К сожалению, я не могу придумать алгоритм обнаружения функции (более похожий на алгоритм обнаружения ошибок), поэтому, я думаю, вам нужно вернуться к хорошему старому обнаружению браузера. Также моя реализация не может обрабатывать строки как первый аргумент и не передает дополнительные аргументы внутренней функции. Наконец, небезопасно вызывать этот метод дважды, поэтому используйте его на свой страх и риск (и не стесняйтесь его улучшать)!

(Примечание. Для нашей библиотеки мы прекратим использование setInterval с этого момента и вместо этого перепишем несколько частей кода, которые полагаются на него, чтобы напрямую использовать setTimeout.)