Приложение Angular Firebase дает сбой через 20 часов с выделением +1 гигабайта памяти

13

Я обнаружил, что использование AngularFireAuthModulefrom '@angular/fire/auth';вызывает утечку памяти, которая приводит к сбою браузера через 20 часов.

Версия:

Я использую последнюю версию, обновленную сегодня, используя ncu -u для всех пакетов.

Угловой огонь: "@angular/fire": "^5.2.3",

Firebase версия: "firebase": "^7.5.0",

Как воспроизвести:

Я сделал минимально воспроизводимый код в редакторе StackBliztz

Вот ссылка для тестирования ошибки непосредственно в тесте StackBlizt

Симптом:

Вы можете проверить, что код ничего не делает. Это просто печатает привет мир. Однако объем памяти JavaScript, используемой приложением Angular, увеличивается на 11 кбит / с (Chrome Task Manager CRTL + ESC). Через 10 часов, оставив браузер открытым, объем используемой памяти достигает примерно 800 МБ (объем памяти составляет около 1,6 ГБ !)

В результате браузеру не хватает памяти, а вкладка 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){

  }
}

Вопрос :

Это может быть ошибкой в ​​реализации FirebaseAuth, и я уже открыл проблему Github, но я ищу обходной путь для этой проблемы. Я отчаянно нуждаюсь в решении. Я не против, даже если сеансы через вкладки не синхронизированы. Мне не нужна эта функция. Я где-то читал, что

если вам не требуется эта функциональность, усилия по модульности Firebase V6 позволят вам переключиться на localStorage, в котором есть события хранения для обнаружения изменений в кросс-таблицах, и, возможно, предоставят вам возможность определить собственный интерфейс хранения.

Если это единственное решение, как это реализовать?

Мне просто нужно любое решение, которое останавливает это ненужное увеличение числа слушателей, потому что оно замедляет работу компьютера и приводит к сбою моего приложения. Мое приложение должно работать более 20 часов, поэтому из-за этой проблемы его нельзя использовать. Я отчаянно нуждаюсь в решении.

TSR
источник
Я не смог воспроизвести вашу проблему на вашем примере
Сергей
@SergeyMell Вы использовали код, который я разместил на StackBlitz?
TSR
Да. На самом деле, я говорю об этом.
Сергей Мелл
Попробуйте скачать код и запустить его локально. Я также загрузил его на диск на всякий случай drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…
TSR

Ответы:

7

TLDR: увеличение числа слушателей является ожидаемым поведением и будет сброшено при сборке мусора. Ошибка, которая вызывает утечки памяти в Firebase Auth, уже была исправлена ​​в Firebase v7.5.0, см. # 1121 , проверьте, package-lock.jsonчтобы убедиться, что вы используете правильную версию. Если вы не уверены, переустановите firebaseпакет.

Предыдущие версии Firebase опрашивали IndexedDB через цепочку Promise, что приводит к утечкам памяти, см. 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();

Исправлено в последующих версиях с использованием нерекурсивных вызовов функций:

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


Относительно линейно возрастающего числа слушателей:

Ожидается линейное увеличение числа слушателей, поскольку именно это Firebase делает для опроса IndexedDB. Тем не менее, слушатели будут удалены, когда GC захочет.

Прочтите выпуск 576302: неправильно отображается утечка памяти (слушатели xhr и загрузка)

V8 периодически выполняет Minor GC, что приводит к небольшим падениям размера кучи. Вы можете увидеть их на карте пламени. Однако второстепенные сборщики мусора могут не собирать весь мусор, что, очевидно, происходит для слушателей.

Кнопка на панели инструментов вызывает Major GC, который способен собирать слушателей.

DevTools старается не мешать работающему приложению, поэтому он не заставляет GC работать самостоятельно.


Чтобы подтвердить, что отсоединенные слушатели являются сборщиком мусора, я добавил этот фрагмент для давления на кучу JS, заставляя GC запускать:

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

Слушатели мусор

Как вы можете видеть, отключенные слушатели периодически удаляются при запуске GC.



Схожие вопросы по стеку и GitHub, касающиеся количества слушателей и утечек памяти:

  1. Слушатели в результатах профилирования производительности инструментов разработчика Chrome
  2. Слушатели JavaScript продолжают расти
  3. Простое приложение, вызывающее утечку памяти?
  4. $ http 'GET' утечка памяти (НЕ!) - количество слушателей (AngularJS v.1.4.7 / 8)
Джошуа Чан
источник
Я подтверждаю, что использую 7.5.0 и тестировал несколько раз в разных средах. Даже this.auth.auth.setPersistence ('none') не предотвращает утечку памяти. Пожалуйста, проверьте это самостоятельно, используя код здесь stackblitz.com/edit/angular-zuabzz
TSR
каковы ваши шаги тестирования? Нужно ли оставлять его на ночь, чтобы увидеть сбой моего браузера? В моем случае номер слушателя всегда сбрасывается после включения GC, и память всегда возвращается к 160 Мб.
Джошуа Чан
@TSR вызов this.auth.auth.setPersistence('none')в ngOnInitвместо конструктора , чтобы отключить настойчивость.
Джошуа Чан
@JoshuaChan имеет ли значение, когда вызывать метод службы? Он внедряется в конструктор и доступен прямо в его теле. Почему это должно войти ngOnInit?
Сергей
@ Сергей в основном за лучшую практику. Но для этого конкретного случая я запустил профилирование ЦП для обоих способов вызова setPersistenceи обнаружил, что если это делается в конструкторе, то вызовы функций по-прежнему выполняются в IndexedDB, тогда как, если это происходит ngOnInit, в IndexedDB не было никаких вызовов, не совсем конечно, почему
Джошуа Чан