Aplikasi Angular Firebase mogok setelah 20 jam dengan alokasi memori +1 gigabyte

13

Saya menemukan bahwa menggunakan AngularFireAuthModuledari '@angular/fire/auth'; menyebabkan kebocoran memori yang menyebabkan crash browser setelah 20 jam.

Versi: kapan:

Saya menggunakan versi terbaru yang diperbarui hari ini menggunakan ncu -u untuk semua paket.

Api sudut: "@angular/fire": "^5.2.3",

Versi Firebase: "firebase": "^7.5.0" ,

Cara mereproduksi:

Saya membuat kode reproducible minimum pada editor StackBliztz

Berikut ini tautan untuk menguji bug langsung dari tes StackBlizt

Gejala:

Anda dapat memeriksa sendiri bahwa kode tidak melakukan apa-apa. Itu hanya mencetak halo dunia. Namun, memori JavaScript yang digunakan oleh Aplikasi Angular meningkat sebesar 11 kb / s (Chrome Task Manager CRTL + ESC). Setelah 10 jam membiarkan browser terbuka, memori yang digunakan mencapai sekitar 800 mb (jejak memori sekitar dua kali 1,6 Gb !)

Akibatnya, browser kehabisan memori dan tab chrome mogok.

Setelah penyelidikan lebih lanjut menggunakan profil memori chrome di bawah tab kinerja, saya dengan jelas melihat bahwa jumlah pendengar meningkat 2 setiap detik dan dengan demikian tumpukan JS bertambah.

masukkan deskripsi gambar di sini

Kode yang menyebabkan kebocoran memori:

Saya menemukan bahwa menggunakan AngularFireAuthModule modul menyebabkan kebocoran memori apakah itu disuntikkan dalam componentkonstruktor atau dalam 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){

  }
}

Pertanyaan :

Ini bisa menjadi bug dalam implementasi FirebaseAuth dan saya sudah membuka masalah Github, tetapi saya sedang mencari solusi untuk masalah ini. Saya sangat membutuhkan solusi. Saya tidak keberatan meskipun sesi lintas tab tidak disinkronkan. Saya tidak membutuhkan fitur itu. Saya membaca suatu tempat itu

jika Anda tidak memerlukan fungsi ini, upaya modularisasi Firebase V6 akan memungkinkan Anda untuk beralih ke localStorage yang memiliki peristiwa penyimpanan untuk mendeteksi tab lintas perubahan, dan mungkin akan memberi Anda kemampuan untuk mendefinisikan antarmuka penyimpanan Anda sendiri.

Jika itu satu-satunya solusi, bagaimana cara mengimplementasikannya?

Saya hanya perlu solusi apa pun yang menghentikan peningkatan pendengar yang tidak perlu ini karena memperlambat komputer dan membuat aplikasi saya mogok. Aplikasi saya harus berjalan selama lebih dari 20 jam sehingga sekarang tidak dapat digunakan karena masalah ini. Saya sangat membutuhkan solusi.

TSR
sumber
Saya gagal mereproduksi masalah Anda pada contoh Anda
Sergey Mell
@SergeyMell Apakah Anda menggunakan kode yang saya posting di StackBlitz?
TSR
Iya. Sebenarnya, saya sedang membicarakannya.
Sergey Mell
Coba unduh kode dan jalankan secara lokal. Saya juga mengunggahnya dalam drive kalau-kalau drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…
TSR

Jawaban:

7

TLDR: Meningkatkan jumlah pendengar adalah perilaku yang diharapkan dan akan diatur ulang saat Pengumpulan Sampah. Bug yang menyebabkan kebocoran memori di Firebase Auth telah diperbaiki di Firebase v7.5.0, lihat # 1121 , periksa Anda package-lock.jsonuntuk mengonfirmasi bahwa Anda menggunakan versi yang benar. Jika tidak yakin, instal ulang firebasepaket.

Firebase versi sebelumnya melakukan polling IndexedDB melalui Promise chaining, yang menyebabkan kebocoran memori, lihat JavaScript's 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();

Diperbaiki pada versi selanjutnya menggunakan panggilan fungsi non-rekursif:

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


Mengenai jumlah pendengar yang meningkat secara linear:

Diharapkan peningkatan jumlah pendengar yang meningkat karena inilah yang dilakukan Firebase untuk melakukan polling pada IndexedDB. Namun, pendengar akan dihapus kapan pun GC menginginkannya.

Baca Edisi 576302: kebocoran memori (pendengar xhr & muat) salah

V8 melakukan Minor GC secara berkala, yang menyebabkan tetesan-tetesan kecil dari ukuran heap. Anda benar-benar dapat melihatnya di api chart. Namun, GC minor mungkin tidak mengumpulkan semua sampah, yang jelas terjadi untuk pendengar.

Tombol bilah alat memanggil GC Utama yang dapat mengumpulkan pendengar.

DevTools mencoba untuk tidak mengganggu aplikasi yang sedang berjalan, sehingga tidak memaksa GC sendiri.


Untuk mengonfirmasi bahwa pendengar yang terlepas adalah sampah yang dikumpulkan, saya menambahkan cuplikan ini untuk menekan tumpukan JS, sehingga memaksa GC untuk memicu:

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

Pendengar dikumpulkan dari sampah

Seperti yang Anda lihat, pendengar yang terpisah dihapus secara berkala ketika GC dipicu.



Pertanyaan stackoverflow serupa dan masalah GitHub mengenai jumlah pendengar dan kebocoran memori:

  1. Pendengar dalam hasil profil kinerja alat Chrome dev
  2. Pendengar JavaScript terus meningkat
  3. Aplikasi sederhana yang menyebabkan kebocoran memori?
  4. $ http 'GET' memory leak (NOT!) - jumlah pendengar (AngularJS v.1.4.7 / 8)
Joshua Chan
sumber
Saya mengonfirmasi menggunakan 7.5.0 dan diuji berulang kali pada lingkungan yang berbeda. Bahkan this.auth.auth.setPersistence ('tidak ada') tidak mencegah kebocoran memori. Silakan mengujinya sendiri menggunakan kode di sini stackblitz.com/edit/angular-zuabzz
TSR
apa langkah pengujian anda? Apakah saya harus membiarkannya semalaman untuk melihat browser saya rusak? Dalam kasus saya, nomor pendengar selalu diatur ulang setelah tendangan GC masuk dan memori selalu kembali ke 160mb.
Joshua Chan
Panggilan @TSR this.auth.auth.setPersistence('none')di ngOnInitbukannya konstruktor kegigihan menonaktifkan.
Joshua Chan
@ JoshuaChan apakah penting kapan harus memanggil metode layanan? Itu sedang disuntikkan di konstruktor dan tersedia tepat di tubuh itu. Kenapa harus masuk ngOnInit?
Sergey
@Sergey kebanyakan untuk praktik terbaik. Tetapi untuk kasus khusus ini, saya menjalankan profil CPU untuk kedua cara pemanggilan setPersistencedan menemukan bahwa jika hal itu dilakukan dalam konstruktor, pemanggilan fungsi masih dilakukan ke IndexedDB, sedangkan jika itu dilakukan dalam ngOnInit, tidak ada panggilan yang dilakukan ke IndexedDB, tidak persis yakin mengapa
Joshua Chan