แอป Angular Firebase ขัดข้องหลังจาก 20 ชั่วโมงด้วยการจัดสรรหน่วยความจำ +1 กิกะไบต์


13

ฉันพบว่าการใช้AngularFireAuthModuleจาก '@angular/fire/auth';ทำให้หน่วยความจำรั่วซึ่งเกิดปัญหากับเบราว์เซอร์หลังจาก 20 ชั่วโมง

เวอร์ชัน:

ฉันใช้เวอร์ชั่นล่าสุดที่อัพเดทวันนี้โดยใช้ ncu -u สำหรับแพ็คเกจทั้งหมด

ไฟเชิงมุม: "@angular/fire": "^5.2.3",

รุ่น Firebase: "firebase": "^7.5.0",

วิธีการทำซ้ำ:

ฉันทำรหัสขั้นต่ำที่ทำซ้ำได้บนตัวแก้ไข StackBliztz

นี่คือลิงค์สำหรับทดสอบข้อบกพร่องโดยตรงทดสอบ StackBlizt

อาการ:

คุณสามารถตรวจสอบตัวเองว่ารหัสไม่ทำอะไรเลย มันพิมพ์สวัสดีชาวโลก อย่างไรก็ตามหน่วยความจำ JavaScript ที่แอปเชิงมุมใช้เพิ่มขึ้น11 kb / s (Chrome Task Manager CRTL + ESC) หลังจากเปิดเบราว์เซอร์ทิ้งไว้ 10 ชั่วโมงหน่วยความจำที่ใช้จะอยู่ที่ประมาณ800 mb (ขนาดหน่วยความจำประมาณ1.6 Gb !)

เป็นผลให้เบราว์เซอร์มีหน่วยความจำไม่เพียงพอและแท็บ Chrome ขัดข้อง

หลังจากการตรวจสอบเพิ่มเติมโดยใช้การทำโปรไฟล์หน่วยความจำของ chrome ภายใต้แท็บประสิทธิภาพการทำงานฉันสังเกตเห็นได้อย่างชัดเจนว่าจำนวนผู้ฟังเพิ่มขึ้น 2 ทุก ๆ วินาทีดังนั้น JS ฮีปจึงเพิ่มขึ้นตามลำดับ

ป้อนคำอธิบายรูปภาพที่นี่

รหัสที่ทำให้หน่วยความจำรั่ว:

ผมพบว่าการใช้AngularFireAuthModule โมดูลทำให้เกิดการรั่วไหลของหน่วยความจำไม่ว่าจะถูกฉีดในตัวสร้างหรือในcomponentservice

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 Auth ได้รับการแก้ไขแล้วใน Firebase v7.5.0 ดู# 1121ตรวจสอบpackage-lock.jsonเพื่อยืนยันว่าคุณใช้เวอร์ชั่นที่ถูกต้อง หากไม่แน่ใจให้ติดตั้งfirebaseแพ็คเกจใหม่

Firebase รุ่นก่อนหน้านี้ทำการสำรวจ IndexedDB ผ่าน Promise chaining ซึ่งทำให้หน่วยความจำรั่วดูที่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 ดำเนินการไมเนอร์ GC เป็นระยะซึ่งทำให้ขนาดฮีปลดลงเล็กน้อย คุณสามารถเห็นพวกเขาบนแผนภูมิเปลวไฟ อย่างไรก็ตาม GCs เล็กน้อยอาจไม่เก็บขยะทั้งหมดซึ่งเกิดขึ้นกับผู้ฟังอย่างเห็นได้ชัด

ปุ่มแถบเครื่องมือจะเรียก Major GC ที่สามารถรวบรวมผู้ฟังได้

DevTools พยายามที่จะไม่ยุ่งเกี่ยวกับแอปพลิเคชันที่ทำงานอยู่ดังนั้นจึงไม่ได้บังคับให้ GC ทำงานด้วยตนเอง


เพื่อยืนยันว่าผู้ฟังที่ดึงออกมาเป็นคนเก็บขยะฉันได้เพิ่มตัวอย่างนี้เพื่อกดดัน JS heap ดังนั้นบังคับให้ GC เรียกใช้:

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

ผู้ฟังเก็บขยะ

อย่างที่คุณเห็นผู้ฟังเดี่ยวจะถูกลบออกเป็นระยะเมื่อมีการเรียกใช้ GC



คำถาม stackoverflow ที่คล้ายกันและปัญหา GitHub เกี่ยวกับจำนวนผู้ฟังและการรั่วไหลของหน่วยความจำ:

  1. ผู้ฟังในผลลัพธ์การทำโปรไฟล์ประสิทธิภาพของเครื่องมือ dev dev
  2. ตัวรับฟัง JavaScript เพิ่มขึ้นเรื่อย ๆ
  3. แอพง่าย ๆ ที่ทำให้หน่วยความจำรั่ว
  4. $ http 'รับ' หน่วยความจำรั่ว (ไม่!) - จำนวนผู้ฟัง (AngularJS v.1.4.7 / 8)

ฉันยืนยันการใช้ 7.5.0 และทดสอบหลายครั้งในสภาพแวดล้อมที่แตกต่างกัน แม้แต่ this.auth.auth.setPersistence ('none') ไม่ได้ป้องกันหน่วยความจำรั่ว กรุณาทดสอบด้วยตัวเองโดยใช้รหัสที่นี่stackblitz.com/edit/angular-zuabzz
TSR

ขั้นตอนการทดสอบของคุณคืออะไร? ฉันต้องทิ้งไว้ข้ามคืนหรือไม่เพื่อดูว่าเบราว์เซอร์พังหรือไม่ ในกรณีของฉันหมายเลขฟังจะรีเซ็ตหลังจากที่เริ่มเล่น GC และหน่วยความจำจะกลับไปที่ 160mb เสมอ
Joshua Chan

@TSR โทรthis.auth.auth.setPersistence('none')เข้าngOnInitแทนตัวสร้างเพื่อปิดการใช้งานการติดตา
Joshua Chan

@JoshuaChan มันสำคัญเมื่อไรที่จะเรียกวิธีการบริการ? มันถูกฉีดเข้าไปในตัวสร้างและมีอยู่ในตัวมัน ทำไมมันควรเข้าไปข้างในngOnInit?
Sergey

@ Sergey ส่วนใหญ่สำหรับการปฏิบัติที่ดีที่สุด แต่สำหรับกรณีเฉพาะนี้ฉันรันการทำโปรไฟล์ของ CPU สำหรับวิธีการโทรทั้งสองsetPersistenceและพบว่าหากทำใน Constructor การเรียกใช้ฟังก์ชันจะยังคงทำกับ IndexedDB ในขณะที่ถ้าเสร็จแล้วจะngOnInitไม่มีการเรียกไปยัง IndexedDB แม้ว่าทำไม
โจชัวชาน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.