Aplikacja Angular Firebase ulega awarii po 20 godzinach z +1 gigabajtem alokacji pamięci

13

Odkryłem, że użycie AngularFireAuthModulefrom '@angular/fire/auth';powoduje wyciek pamięci, który powoduje awarię przeglądarki po 20 godzinach.

Wersja:

Używam najnowszej wersji zaktualizowanej dzisiaj przy użyciu ncu -u dla wszystkich pakietów.

Kątowy ogień: "@angular/fire": "^5.2.3",

Firebase wersja: "firebase": "^7.5.0",

Jak powielać:

Zrobiłem minimalny powtarzalny kod w edytorze StackBliztz

Oto link do bezpośredniego przetestowania błędu test StackBlizt

Objaw:

Możesz sprawdzić, czy kod nic nie robi. To po prostu drukuje witaj świecie. Jednak pamięć JavaScript używana przez aplikację Angular zwiększa się o 11 kb / s (Chrome Task Manager CRTL + ESC). Po 10 godzinach pozostawiania otwartej przeglądarki używana pamięć osiąga około 800 mb (ślad zajmuje około dwa razy 1,6 Gb !)

W rezultacie w przeglądarce brakuje pamięci, a karta chrom ulega awarii.

Po dalszych badaniach przy użyciu profilowania pamięci chrome pod zakładką wydajności wyraźnie zauważyłem, że liczba słuchaczy rośnie o 2 co sekundę, a zatem stos JS odpowiednio rośnie.

wprowadź opis zdjęcia tutaj

Kod powodujący wyciek pamięci:

Odkryłem, że użycie AngularFireAuthModule modułu powoduje wyciek pamięci, niezależnie od tego, czy jest wstrzykiwany do componentkonstruktora, czy do service.

import { Component } from '@angular/core';
import {AngularFireAuth} from '@angular/fire/auth';
import {AngularFirestore} from '@angular/fire/firestore';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'memoryleak';
  constructor(public auth: AngularFireAuth){

  }
}

Pytanie :

Może to być błąd w implementacji FirebaseAuth i już otworzyłem problem z Githubem, ale szukam obejścia tego problemu. Desperacko szukam rozwiązania. Nie mam nic przeciwko, nawet jeśli sesje między kartami nie są zsynchronizowane. Nie potrzebuję tej funkcji. Przeczytałem gdzieś to

jeśli nie potrzebujesz tej funkcji, wysiłki modularyzacyjne Firebase V6 pozwolą ci przejść na localStorage, który ma zdarzenia pamięci do wykrywania zmian między kartami, i ewentualnie zapewni ci możliwość zdefiniowania własnego interfejsu pamięci.

Jeśli to jedyne rozwiązanie, jak to wdrożyć?

Potrzebuję tylko rozwiązania, które zatrzyma ten niepotrzebny wzrost liczby słuchaczy, ponieważ spowalnia komputer i powoduje awarię mojej aplikacji. Moja aplikacja musi działać przez ponad 20 godzin, więc nie można jej już używać z powodu tego problemu. Desperacko szukam rozwiązania.

TSR
źródło
Nie udało mi się odtworzyć twojego problemu na twoim przykładzie
Sergey Mell,
@SergeyMell Czy używałeś kodu, który opublikowałem na StackBlitz?
TSR,
Tak. Właściwie to mówię o tym.
Sergey Mell,
Spróbuj pobrać kod i uruchomić go lokalnie. Przesłałem go również na dysk na wszelki wypadek drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…
TSR

Odpowiedzi:

7

TLDR: Oczekiwany jest wzrost liczby słuchaczy i zostanie zresetowany po odśmiecaniu. Błąd powodujący przecieki pamięci w Firebase Auth został już naprawiony w Firebase v7.5.0, patrz # 1121 , sprawdź, czy package-lock.jsonużywasz właściwej wersji. W razie wątpliwości zainstaluj ponownie firebasepakiet.

Poprzednie wersje Firebase odpytywały IndexedDB poprzez łańcuch obietnic, który powoduje wycieki pamięci, patrz JavaScript Promise Leaks Memory

var repeat = function() {
  self.poll_ =
      goog.Timer.promise(fireauth.storage.IndexedDB.POLLING_DELAY_)
      .then(goog.bind(self.sync_, self))
      .then(function(keys) {
        // If keys modified, call listeners.
        if (keys.length > 0) {
          goog.array.forEach(
              self.storageListeners_,
              function(listener) {
                listener(keys);
              });
        }
      })
      .then(repeat)
      .thenCatch(function(error) {
        // Do not repeat if cancelled externally.
        if (error.message != fireauth.storage.IndexedDB.STOP_ERROR_) {
          repeat();
        }
      });
  return self.poll_;
};
repeat();

Naprawiono w kolejnych wersjach za pomocą nierekurencyjnych wywołań funkcji:

var repeat = function() {
  self.pollTimerId_ = setTimeout(
      function() {
        self.poll_ = self.sync_()
            .then(function(keys) {
              // If keys modified, call listeners.
              if (keys.length > 0) {
                goog.array.forEach(
                    self.storageListeners_,
                    function(listener) {
                      listener(keys);
                    });
              }
            })
            .then(function() {
              repeat();
            })
            .thenCatch(function(error) {
              if (error.message != fireauth.storage.IndexedDB.STOP_ERROR_) {
                repeat();
              }
            });
      },
      fireauth.storage.IndexedDB.POLLING_DELAY_);
};
repeat();


Jeśli chodzi o liniowo rosnącą liczbę słuchaczy:

Oczekuje się liniowego wzrostu liczby słuchaczy, ponieważ to właśnie robi Firebase, aby sondować IndexedDB. Jednak nasłuchiwacze zostaną usunięci, ilekroć GC chce.

Przeczytaj numer 576302: Nieprawidłowe wyświetlanie wycieku pamięci (nasłuchiwania i ładowanie)

V8 wykonuje pomniejsze GC okresowo, co powoduje te małe spadki wielkości sterty. Możesz je zobaczyć na wykresie płomieni. Mniejsze GC mogą jednak nie zbierać wszystkich śmieci, co oczywiście zdarza się dla słuchaczy.

Przycisk paska narzędzi wywołuje Major GC, który może gromadzić słuchaczy.

DevTools stara się nie zakłócać działania uruchomionej aplikacji, więc nie wymusza sam GC.


Aby potwierdzić, że odłączone nasłuchiwania są gromadzone w pamięci, dodałem ten fragment kodu, aby wywrzeć nacisk na stos JS, zmuszając w ten sposób GC do uruchomienia:

var x = ''
setInterval(function () {
  for (var i = 0; i < 10000; i++) {
    x += 'x'
  }
}, 1000)

Słuchacze są wyrzucani na śmieci

Jak widać, odłączone odbiorniki są okresowo usuwane po uruchomieniu GC.



Podobne pytania o przepełnienie stosu i problemy z GitHub dotyczące liczby słuchaczy i wycieków pamięci:

  1. Słuchacze w wynikach profilowania wydajności narzędzi programistycznych Chrome
  2. Liczba słuchaczy JavaScript stale rośnie
  3. Prosta aplikacja powodująca wyciek pamięci?
  4. $ http „GET” wyciek pamięci (NIE!) - liczba słuchaczy (AngularJS v.1.4.7 / 8)
Joshua Chan
źródło
Potwierdzam użycie wersji 7.5.0 i testowałem wiele razy w różnych środowiskach. Nawet this.auth.auth.setPersistence („none”) nie zapobiega wyciekowi pamięci. Sprawdź to sam, korzystając z kodu tutaj stackblitz.com/edit/angular-zuabzz
TSR
jakie są twoje kroki testowe? Czy muszę zostawić go na noc, aby zobaczyć awarię przeglądarki? W moim przypadku numer słuchacza zawsze resetuje się po uruchomieniu GC, a pamięć zawsze wraca do 160 MB.
Joshua Chan,
@TSR wezwanie this.auth.auth.setPersistence('none')w ngOnInitzamiast konstruktora, aby wyłączyć funkcję utrwalania.
Joshua Chan,
@JoshuaChan czy ma znaczenie, kiedy wywołać metodę usługi? Jest wstrzykiwany do konstruktora i dostępny bezpośrednio w ciele. Dlaczego powinien wejść ngOnInit?
Siergiej
@Sergey głównie dla najlepszych praktyk. Ale w tym konkretnym przypadku uruchomiłem profilowanie procesora dla obu sposobów wywoływania setPersistencei okazało się, że jeśli jest to zrobione w konstruktorze, wywołania funkcji są nadal wykonywane dla IndexedDB, podczas gdy jeśli jest zrobione w ngOnInit, nie wykonuje się żadnych wywołań dla IndexedDB, nie do końca jasne dlaczego
Joshua Chan,