Angular Firebase 应用程序在 20 小时后崩溃,内存分配为 +1 GB

TSR*_*TSR 16 firebase angularfire angular

我发现使用AngularFireAuthModulefrom '@angular/fire/auth';会导致内存泄漏,导致 20 小时后浏览器崩溃。

版本:

我使用今天更新的最新版本,对所有软件包使用 ncu -u。

角火: "@angular/fire": "^5.2.3",

Firebase 版本: "firebase": "^7.5.0",

如何重现:

我在StackBliztz 编辑器上做了一个最小的可重现代码

这里是直接测试bug的链接StackBlizt test

症状:

您可以自己检查代码是否没有任何作用。它只是打印 hello world。但是,Angular App 使用的 JavaScript 内存增加了11 kb/s(Chrome 任务管理器 CRTL+ESC)。打开浏览器 10 小时后,使用的内存达到约800 mb(内存占用量约为1.6 Gb 的两倍!)

结果,浏览器内存不足,chrome 选项卡崩溃。

在性能选项卡下使用chrome的内存分析进一步调查后,我清楚地注意到侦听器的数量每秒增加2个,因此JS堆相应增加。

在此处输入图片说明

导致内存泄漏的代码:

我发现使用该AngularFireAuthModule 模块会导致内存泄漏,无论是在component构造函数中还是在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){

  }
}
Run Code Online (Sandbox Code Playgroud)

问题

这可能是 FirebaseAuth 实现中的一个错误,我已经打开了一个 Github 问题,但我正在寻找解决此问题的方法。我迫切需要一个解决方案。即使跨选项卡的会话不同步,我也不介意。我不需要那个功能。我在某处读到

如果您不需要此功能,Firebase V6 模块化工作将允许您切换到 localStorage,它具有用于检测交叉表更改的存储事件,并且可能会为您提供定义自己的存储接口的能力。

如果这是唯一的解决方案,那么如何实施?

我只需要任何解决方案来阻止这种不必要的侦听器增加,因为它会减慢计算机速度并使我的应用程序崩溃。我的应用程序需要运行 20 多个小时,因此由于此问题,它现在无法使用。我迫切需要一个解决方案。

Jos*_*han 7

TLDR:增加侦听器数量是预期行为,将在垃圾收集时重置。Firebase 身份验证中导致内存泄漏的错误已在 Firebase v7.5.0 中修复,请参阅#1121,检查您package-lock.json以确认您使用的是正确的版本。如果不确定,请重新安装firebase软件包。

以前版本的 Firebase 通过 Promise 链轮询 IndexedDB,这会导致内存泄漏,请参阅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();
Run Code Online (Sandbox Code Playgroud)

在使用非递归函数调用的后续版本中修复:

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();
Run Code Online (Sandbox Code Playgroud)

关于线性增加的听众数量:

预计会线性增加侦听器数量,因为 Firebase 正在这样做以轮询 IndexedDB。但是,只要 GC 需要,就会删除侦听器。

阅读问题 576302:错误地显示内存(侦听器 xhr 和负载)泄漏

V8 会定期执行 Minor GC,这会导致堆大小的小幅下降。您实际上可以在火焰图上看到它们。然而,次要 GC 可能不会收集所有垃圾,这显然发生在侦听器上。

工具栏按钮调用能够收集侦听器的 Major GC。

DevTools 尽量不干扰正在运行的应用程序,因此它不会自行强制执行 GC。


为了确认分离的侦听器被垃圾收集,我添加了这个片段来对 JS 堆施压,从而强制 GC 触发:

var x = ''
setInterval(function () {
  for (var i = 0; i < 10000; i++) {
    x += 'x'
  }
}, 1000)
Run Code Online (Sandbox Code Playgroud)

侦听器被垃圾收集

如您所见,触发 GC 时会定期删除分离的侦听器。



关于监听器数量和内存泄漏的类似 stackoverflow 问题和 GitHub 问题:

  1. Chrome 开发工具性能分析结果中的侦听器
  2. JavaScript 侦听器不断增加
  3. 导致内存泄漏的简单应用程序?
  4. $http 'GET' 内存泄漏(不是!)--侦听器的数量(AngularJS v.1.4.7/8)