L'app Angular Firebase si arresta in modo anomalo dopo 20 ore con +1 gigabyte di allocazione della memoria


13

Ho scoperto che l'utilizzo di AngularFireAuthModulefrom '@angular/fire/auth';provoca una perdita di memoria che provoca l'arresto anomalo del browser dopo 20 ore.

Versione:

Uso l'ultima versione aggiornata oggi usando ncu -u per tutti i pacchetti.

Fuoco angolare: "@angular/fire": "^5.2.3",

Versione Firebase: "firebase": "^7.5.0",

Come riprodurre:

Ho creato un codice minimo riproducibile sull'editor StackBliztz

Ecco il link per testare direttamente il bug StackBlizt test

Sintomo:

Puoi verificare che il codice non faccia nulla. Stampa solo ciao mondo. Tuttavia, la memoria JavaScript utilizzata dall'app angolare aumenta di 11 kb / s (Chrome Task Manager CRTL + ESC). Dopo 10 ore di apertura del browser, la memoria utilizzata raggiunge circa 800 mb (il footprint della memoria è circa due volte 1,6 Gb !)

Di conseguenza, il browser esaurisce la memoria e la scheda Chrome si arresta in modo anomalo.

Dopo ulteriori indagini utilizzando la profilazione della memoria di Chrome nella scheda delle prestazioni, ho notato chiaramente che il numero di ascoltatori aumenta di 2 ogni secondo e quindi l'heap JS aumenta di conseguenza.

inserisci qui la descrizione dell'immagine

Codice che causa la perdita di memoria:

Ho scoperto che l'uso del AngularFireAuthModule modulo provoca la perdita di memoria sia che sia iniettata in un componentcostruttore o in a 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){

  }
}

Domanda :

Potrebbe essere un bug nell'implementazione di FirebaseAuth e ho già aperto un problema con Github, ma sto cercando una soluzione per questo problema. Sono alla disperata ricerca di una soluzione. Non mi dispiace anche se le sessioni tra le schede non sono sincronizzate. Non ho bisogno di quella funzione. L'ho letto da qualche parte

se non si necessita di questa funzionalità, gli sforzi di modularizzazione di Firebase V6 ti permetteranno di passare a localStorage che ha eventi di archiviazione per il rilevamento di modifiche a campi incrociati e forse ti darà la possibilità di definire la tua interfaccia di archiviazione.

Se questa è l'unica soluzione, come implementarla?

Ho solo bisogno di qualsiasi soluzione che fermi questo inutile aumento dell'ascoltatore perché rallenta il computer e blocca la mia app. La mia app deve funzionare per più di 20 ore, quindi ora è inutilizzabile a causa di questo problema. Sono alla disperata ricerca di una soluzione.



Non sono riuscito a riprodurre il tuo problema sul tuo esempio
Sergey Mell,

@SergeyMell Hai usato il codice che ho pubblicato su StackBlitz?
TSR

Sì. In realtà, ne sto parlando.
Sergey Mell,

Prova a scaricare il codice ed eseguilo localmente. L'ho anche caricato nell'unità nel caso in cui drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…
TSR

Risposte:


7

TLDR: il numero di ascoltatori in aumento è un comportamento previsto e verrà reimpostato su Garbage Collection. Il bug che causa perdite di memoria in Firebase Auth è già stato corretto in Firebase v7.5.0, vedere # 1121 , controllare il proprio package-lock.jsonper confermare che si sta utilizzando la versione corretta. In caso di dubbi, reinstallare il firebasepacchetto.

Le versioni precedenti di Firebase eseguivano il polling di IndexedDB tramite concatenamento Promise, che causa perdite di memoria, vedere Memoria perdite promesse di JavaScript

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

Risolto nelle versioni successive utilizzando chiamate di funzione non ricorsive:

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


Per quanto riguarda il numero di ascoltatori che aumenta in modo lineare:

È previsto un aumento lineare del conteggio degli ascoltatori poiché questo è ciò che Firebase sta facendo per eseguire il polling di IndexedDB. Tuttavia, gli ascoltatori verranno rimossi ogni volta che il GC lo desidera.

Leggi il problema 576302: visualizzazione errata della perdita di memoria (ascoltatori xhr e caricamento)

V8 esegue periodicamente GC minori, il che provoca quelle piccole gocce della dimensione dell'heap. Puoi effettivamente vederli sul diagramma di fiamma. Tuttavia, i GC minori potrebbero non raccogliere tutta la spazzatura, cosa che ovviamente accade per gli ascoltatori.

Il pulsante della barra degli strumenti richiama il GC principale che è in grado di raccogliere ascoltatori.

DevTools tenta di non interferire con l'applicazione in esecuzione, quindi non impone GC da solo.


Per confermare che i listener separati vengono raccolti in modo inutile, ho aggiunto questo frammento per fare pressione sull'heap JS, costringendo in tal modo GC ad attivare:

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

Gli ascoltatori vengono raccolti

Come puoi vedere, i listener separati vengono rimossi periodicamente quando viene attivato GC.



Domande simili sullo stackoverflow e problemi di GitHub relativi al numero di listener e perdite di memoria:

  1. Ascoltatori nei risultati di profilazione delle prestazioni degli strumenti di sviluppo di Chrome
  2. I listener JavaScript continuano ad aumentare
  3. Semplice app che causa una perdita di memoria?
  4. Perdita di memoria $ GET (NON!) - numero di ascoltatori (AngularJS v.1.4.7 / 8)

Confermo di utilizzare 7.5.0 e testato più volte in ambienti diversi. Anche this.auth.auth.setPersistence ('none') non impedisce la perdita di memoria. Provalo
TSR

quali sono le tue fasi di test? Devo lasciarlo durante la notte per vedere il mio browser in crash? Nel mio caso, il numero dell'ascoltatore si reimposta sempre dopo l'avvio del GC e la memoria torna sempre a 160mb.
Joshua Chan,

@TSR chiama this.auth.auth.setPersistence('none')al ngOnInitposto del costruttore per disabilitare la persistenza.
Joshua Chan,

@JoshuaChan importa quando chiamare un metodo di un servizio? Viene iniettato in un costruttore e disponibile proprio nel suo corpo. Perché dovrebbe entrare ngOnInit?
Sergey

@Sergey principalmente per le migliori pratiche. Ma per questo caso specifico, ho eseguito il profiling della CPU per entrambi i modi di chiamare setPersistencee ho scoperto che se viene eseguito nel costruttore, le chiamate di funzione vengono comunque effettuate su IndexedDB, mentre se viene eseguita in ngOnInit, nessuna chiamata viene eseguita su IndexedDB, non esattamente certo perché
Joshua Chan il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.