AngularFireAuthModule
from 을 사용하면 '@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 시간 이상 실행해야하므로 이제이 문제로 인해 사용할 수 없습니다. 해결책이 절실합니다.
답변
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 문제 :