+1 기가 바이트의 메모리 할당으로 20 시간 후 Angular Firebase 앱 충돌


13

AngularFireAuthModulefrom 을 사용하면 '@angular/fire/auth';20 시간 후에 브라우저가 충돌하는 메모리 누수가 발생 한다는 것을 알았습니다 .

버전:

모든 패키지에 대해 ncu -u를 사용하여 오늘 업데이트 된 최신 버전을 사용합니다.

앵귤러 파이어 : "@angular/fire": "^5.2.3",

Firebase 버전 : "firebase": "^7.5.0",

재현하는 방법 :

StackBliztz 편집기 에서 최소한의 재현 가능한 코드를 만들었습니다.

다음은 버그를 직접 테스트하는 링크입니다. StackBlizt 테스트

징후:

코드가 아무것도하지 않는지 스스로 확인할 수 있습니다. 그냥 hello world를 인쇄합니다. 그러나 Angular 앱에서 사용하는 JavaScript 메모리는 11kb / s 증가합니다 (Chrome 작업 관리자 CRTL + ESC). 브라우저를 연 상태로 10 시간이 지나면 사용 된 메모리는 약 800MB에 도달 합니다 (메모리 풋 프린트는 1.6Gb의 약 두 배입니다 !).

결과적으로 브라우저에 메모리가 부족하고 크롬 탭이 충돌합니다.

성능 탭에서 크롬의 메모리 프로파일 링을 사용하여 추가 조사를 한 후 리스너 수가 초당 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 시간 이상 실행해야하므로 이제이 문제로 인해 사용할 수 없습니다. 해결책이 절실합니다.



귀하의 사례에 대한 귀하의 문제를 재현하지 못했습니다
Sergey Mell

@SergeyMell StackBlitz에 게시 한 코드를 사용 했습니까?
TSR

예. 사실, 나는 그것에 대해 이야기하고 있습니다.
Sergey Mell

코드를 다운로드하여 로컬로 실행하십시오. drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…
TSR

답변:


7

TLDR : 리스너 번호 증가가 예상되는 동작이며 가비지 콜렉션시 재설정됩니다. Firebase 인증에서 메모리 누수를 일으키는 버그는 Firebase v7.5.0에서 이미 수정되었습니다. # 1121을 참조하십시오 package-lock.json. 올바른 버전을 사용하고 있는지 확인하십시오 . 확실하지 않으면 firebase패키지를 다시 설치하십시오 .

이전 버전의 Firebase는 Promise chaining을 통해 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();

비 재귀 함수 호출을 사용하는 후속 버전에서 수정되었습니다.

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를 주기적으로 수행하므로 힙 크기가 작아집니다. 불꽃 차트에서 실제로 볼 수 있습니다. 그러나 작은 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. 자바 스크립트 청취자 증가
  3. 메모리 누수를 일으키는 간단한 앱?
  4. $ http 'GET'메모리 누수 (NOT!)-리스너 수 (AngularJS v.1.4.7 / 8)

7.5.0 사용을 확인하고 다른 환경에서 여러 번 테스트했습니다. this.auth.auth.setPersistence ( 'none')조차도 메모리 누수를 막지 않습니다. 여기에있는 코드를 사용하여 직접 테스트 해보
TSR

테스트 단계는 무엇입니까? 브라우저 충돌을 보려면 밤새 방치해야합니까? 필자의 경우 리스너 번호는 GC 킥 인 후에 항상 재설정되고 메모리는 항상 160MB로 돌아갑니다.
Joshua Chan

@TSR 전화 this.auth.auth.setPersistence('none')에서 ngOnInit대신 비활성화 지속성 생성자.
Joshua Chan

@JoshuaChan은 서비스 메소드를 호출 할 때 중요합니까? 생성자에 주입되어 본문에서 바로 사용할 수 있습니다. 왜 들어가야 ngOnInit합니까?
Sergey

@Sergey는 대부분 모범 사례입니다. 그러나이 특정 경우, 두 가지 호출 방법 모두에 대해 CPU 프로파일 링을 실행 setPersistence하고 생성자에서 수행 된 경우 함수 호출이 여전히 IndexedDB에 수행되는 반면 ngOnInit, 에서 수행되는 경우 IndexedDB 에 대한 호출은 정확하게 수행되지 않았습니다. 확실한 이유
Joshua Chan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.