Die Angular Firebase-App stürzt nach 20 Stunden mit +1 Gigabyte Speicherzuweisung ab

13

Ich habe festgestellt, dass die Verwendung von AngularFireAuthModulefrom '@angular/fire/auth';einen Speicherverlust verursacht, der den Browser nach 20 Stunden zum Absturz bringt.

Ausführung:

Ich verwende die neueste Version, die heute mit ncu -u aktualisiert wurde, für alle Pakete.

Winkelfeuer: "@angular/fire": "^5.2.3",

Firebase-Version : "firebase": "^7.5.0",

So reproduzieren Sie:

Ich habe im StackBliztz-Editor einen reproduzierbaren Mindestcode erstellt

Hier ist der Link, um den Fehler direkt StackBlizt-Test zu testen

Symptom:

Sie können selbst überprüfen, ob der Code nichts bewirkt. Es druckt nur Hallo Welt. Der von der Angular App verwendete JavaScript-Speicher erhöht sich jedoch um 11 kb / s (Chrome Task Manager CRTL + ESC). Nach 10 Stunden, in denen der Browser geöffnet ist, erreicht der verwendete Speicher ca. 800 MB (der Speicherbedarf beträgt etwa zweimal 1,6 GB !).

Infolgedessen hat der Browser nicht mehr genügend Speicher und die Chrome-Registerkarte stürzt ab.

Nach weiteren Untersuchungen unter Verwendung des Speicherprofils von Chrome unter der Registerkarte "Leistung" stellte ich klar fest, dass die Anzahl der Listener jede Sekunde um 2 zunimmt und der JS-Heap entsprechend zunimmt.

Geben Sie hier die Bildbeschreibung ein

Code, der den Speicherverlust verursacht:

Ich fand heraus, dass die Verwendung des AngularFireAuthModule Moduls den Speicherverlust verursacht, unabhängig davon, ob es in einen componentKonstruktor oder in einen injiziert wird 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){

  }
}

Frage :

Es könnte ein Fehler in der Implementierung von FirebaseAuth sein, und ich habe bereits ein Github-Problem geöffnet, suche jedoch nach einer Problemumgehung für dieses Problem. Ich bin verzweifelt nach einer Lösung. Es macht mir nichts aus, auch wenn die Sitzungen über Registerkarten nicht synchronisiert sind. Ich brauche diese Funktion nicht. Ich habe das irgendwo gelesen

Wenn Sie diese Funktionalität nicht benötigen, können Sie mit den Modularisierungsbemühungen von Firebase V6 zu localStorage wechseln, das Speicherereignisse zum Erkennen von Kreuztabellen für Änderungen enthält, und möglicherweise die Möglichkeit bieten, Ihre eigene Speicherschnittstelle zu definieren.

Wenn das die einzige Lösung ist, wie kann man das implementieren?

Ich brauche nur eine Lösung, die diese unnötige Erhöhung des Hörers stoppt, weil sie den Computer verlangsamt und meine App zum Absturz bringt. Meine App muss länger als 20 Stunden laufen, damit sie aufgrund dieses Problems nicht mehr verwendet werden kann. Ich bin verzweifelt nach einer Lösung.

TSR
quelle
Ich konnte Ihr Problem in Ihrem Beispiel nicht reproduzieren
Sergey Mell
@SergeyMell Hast du den Code verwendet, den ich auf StackBlitz gepostet habe?
TSR
Ja. Eigentlich spreche ich darüber.
Sergey Mell
Versuchen Sie, den Code herunterzuladen und lokal auszuführen. Ich habe es auch in Laufwerk hochgeladen, nur für den Fall, dass drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…
TSR

Antworten:

7

TLDR: Das Erhöhen der Listener-Nummer ist ein erwartetes Verhalten und wird bei der Garbage Collection zurückgesetzt. Der Fehler, der zu Speicherverlusten in Firebase Auth führt, wurde bereits in Firebase v7.5.0 behoben. Siehe # 1121. Überprüfen Sie package-lock.json, ob Sie die richtige Version verwenden. Wenn Sie sich nicht sicher sind, installieren Sie das firebasePaket erneut.

In früheren Versionen von Firebase wurde IndexedDB über Promise Chaining abgefragt, was zu Speicherlecks führt. Weitere Informationen finden Sie im 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();

In nachfolgenden Versionen mit nicht rekursiven Funktionsaufrufen behoben:

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


In Bezug auf linear ansteigende Hörerzahl:

Es wird erwartet, dass die Anzahl der Listener linear erhöht wird, da Firebase dies tut, um IndexedDB abzufragen. Listener werden jedoch entfernt, wann immer der GC dies wünscht.

Read Issue 576302: Speicherleck (Listener xhr & load) wird falsch angezeigt

V8 führt regelmäßig Minor GC durch, was zu kleinen Tropfen der Heap-Größe führt. Sie können sie tatsächlich auf der Flammenkarte sehen. Die kleinen GCs sammeln jedoch möglicherweise nicht den gesamten Müll, was offensichtlich für Hörer geschieht.

Die Symbolleistenschaltfläche ruft den Major GC auf, der Listener sammeln kann.

DevTools versucht, die laufende Anwendung nicht zu stören, sodass GC nicht alleine erzwungen wird.


Um zu bestätigen, dass getrennte Listener Müll gesammelt haben, habe ich dieses Snippet hinzugefügt, um den JS-Heap unter Druck zu setzen, wodurch GC gezwungen wird, Folgendes auszulösen:

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

Zuhörer werden Müll gesammelt

Wie Sie sehen können, werden getrennte Listener regelmäßig entfernt, wenn GC ausgelöst wird.



Ähnliche Stackoverflow-Fragen und GitHub-Probleme bezüglich Listener-Nummer und Speicherlecks:

  1. Listener in den Performance Profiling-Ergebnissen der Chrome Dev Tools
  2. JavaScript-Listener nehmen ständig zu
  3. Einfache App, die einen Speicherverlust verursacht?
  4. $ http 'GET' Speicherverlust (NICHT!) - Anzahl der Listener (AngularJS v.1.4.7 / 8)
Joshua Chan
quelle
Ich bestätige die Verwendung von 7.5.0 und habe mehrere Tests in verschiedenen Umgebungen durchgeführt. Selbst this.auth.auth.setPersistence ('none') verhindert den Speicherverlust nicht. Bitte testen Sie es selbst mit dem Code hier stackblitz.com/edit/angular-zuabzz
TSR
Was sind Ihre Testschritte? Muss ich es über Nacht stehen lassen, damit mein Browser abstürzt? In meinem Fall wird die Listener-Nummer nach dem GC-Kick-In immer zurückgesetzt und der Speicher ist immer wieder auf 160 MB.
Joshua Chan
@TSR Anruf this.auth.auth.setPersistence('none')in ngOnInitanstelle des Konstrukteurs zu deaktivieren Ausdauer.
Joshua Chan
@JoshuaChan spielt es eine Rolle, wann eine Methode eines Dienstes aufgerufen werden soll? Es wird in einen Konstruktor injiziert und ist direkt in seinem Körper verfügbar. Warum sollte es hineingehen ngOnInit?
Sergey
@ Sergey meistens für Best Practice. In diesem speziellen Fall habe ich die CPU-Profilerstellung für beide setPersistenceAufrufarten ausgeführt und festgestellt, dass, wenn dies im Konstruktor ausgeführt wird, weiterhin Funktionsaufrufe an IndexedDB ausgeführt werden, während bei dieser Ausführung ngOnInitkeine Aufrufe an IndexedDB vorgenommen wurden, nicht genau sicher warum aber
Joshua Chan