2013-04-09 15 views
10

Podczas testowania naszej biblioteki JavaScript uważam, że znaleźliśmy poważny wyciek pamięci w IE10 (v10.0.9200.16519 - Windows 8 64 bit) Implementacja JavaScript setInterval.Obejście problemu wycieku pamięci setInterval

Prosty przypadek testowy pokazał, że jeśli zmienna jest przechwytywana w zamknięciu funkcji, która jest przekazywana jako argument do późniejszego wykonania, nie wydaje się, aby kiedykolwiek była uprawniona do zbierania śmieci, tzn. Przeglądarka nadal wydaje się zawierać odniesienie do funkcji lub co najmniej zmiennych zamknięcia.

Nasza testcase uruchamia funkcję setInterval tylko raz, a następnie czyści licznik interwału, tzn. Po chwili nie jest już wykonywany żaden kod i nie są już dostępne żadne zmienne (o ile widzę, żadne globale nie są wprowadzane w tym kodzie, z wyjątkiem dla metody do uruchomienia w onload), mimo to proces zajmuje pół gigabajta pamięci (w zależności od liczby iteracji).

Co ciekawe to nie nastąpi, jeśli używamy metody setTimeout zamiast (a także problem robi nie wydają się istnieć w IE9 i aktualne wersje Chrome, FF).

Problem można zobaczyć pod numerem this fiddle.

Uruchom go w świeżej instancji IE10 na Windows 8 i otwórz menedżera zadań, aby obejrzeć wykorzystanie pamięci. Szybko wzrośnie do 350 megabajtów i pozostanie tam po wykonaniu skryptu.

Jest to ważna część problematycznej kawałka kodu:

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

(wiem, że to jest łatwe do naprawienia tego konkretnego kawałek kodu Ale to nie o to chodzi - to tylko najmniejsze. kawałek kodu, który wymyśliliśmy, który odwzorowuje problem, prawdziwy kod faktycznie przechwytuje w zamknięciu this, a obiekt ten nigdy nie jest zbierany śmieci.)

Czy jest jakiś błąd w naszym kodzie lub czy istnieje sposób użycia setInterval gdzie zmienna zamknięcia zawiera odniesienie do al arge obiekt bez wyzwalania wycieku pamięci i bez przywracania połączeń "rekursywnych" setTimeout?

(ja również posted the question on MSDN)

Aktualizacja: Ten problem występuje również w IE10 na Windows 7, ale nie istnieje po przełączeniu na tryb IE9 standardów. Przesłałem to do MS Connect i poinformuję o postępach.

Aktualizacja: Microsoft accepted the issue i poinformował go, który zostanie ustalony w IE11 (wersja preview) - ja nie potwierdziły ten sam, jeszcze

Aktualizacja: IE 11 został oficjalnie wydany (ktoś?) i nie mogę odtworzyć problemu w tej wersji z moim systemem (Win 8.1 Pro 64bit).

+0

Czemu tworząc odstęp, a następnie wyczyszczenie go natychmiast ? Czy to nie jest celem? –

+0

@BradM Uaktualniłem pytanie z wyjaśnieniem - prawdziwy kod jest bardziej skomplikowany i nie zawsze bezwarunkowo kasuje ten interwał. – Sebastian

+0

Czy próbowałeś nie używać funkcji anonimowej, ale zamiast tego referencji funkcji - a następnie jawnie nadpisujesz to odwołanie? – CBroe

Odpowiedz

6

Dla dobra kompletności dodaję ewentualnego obejścia tutaj:

Jak już pisałem (i komentujących sugerowane), można to obejść (nie stała) przez sięgnięcie do setTimeout. Nie jest to trywialne, ponieważ trzeba wykonać pewne księgowanie id.Tu jest mój sugerowane poprawki, które można test and fork from this fiddle:

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

Ideą jest rekursywnie wywołać setTimeout symulować powtarzających setInterval połączeń. W tej implementacji jest niewiele nakładów, ponieważ musi ona wykonywać księgowanie dla zmieniających się id s, więc nie zalecałbym stosowania tej poprawki, chyba że jest to wymagane.

Niestety nie jestem w stanie wymyślić algorytmu wykrywania "funkcji" (bardziej jak algorytmu "wykrywania błędów"), więc domyślam się, że musisz powrócić do starego dobrego wykrywania przeglądarki. Również moja implementacja nie może traktować ciągów jako pierwszego argumentu i nie przekazuje dodatkowych argumentów do wewnętrznej funkcji. Wreszcie nie jest bezpiecznie wywołać tę metodę dwukrotnie, więc używaj jej na własne ryzyko (i zachęcamy do jej poprawy)!

(Uwaga: W naszej bibliotece będziemy zaprzestać używania setInterval od teraz i zamiast przepisać kilka części w kodzie, które opierają się na nim używać setTimeout bezpośrednio.)

Powiązane problemy