BLOCKING_EXECUTOR in ExecutorsRegistrar uses unbounded CachedThreadPool — causes 68+ threads on low-memory devices

オープン
#7,899 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
52/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
android, java

調査の方向性

firebase-common/.../concurrent/ExecutorsRegistrar.java から始め、BLOCKING_EXECUTOR がどのように作成され、使用されているかを調査します。issue で説明されている上限付き ThreadPoolExecutor と LinkedBlockingQueue の選択肢を比較し、その後、低メモリのシナリオを再現するか、スレッドダンプを調査します。blocking thread の作成が制限され、アイドル状態のスレッドの破棄と executor の動作が適切なままであれば完了です。

索引モデルが issue の本文から書いたものです。

説明

api: core api: messaging

Description

ExecutorsRegistrar.BLOCKING_EXECUTOR uses Executors.newCachedThreadPool() with no upper bound on thread creation:

// firebase-common/.../concurrent/ExecutorsRegistrar.java
static final Lazy<ScheduledExecutorService> BLOCKING_EXECUTOR =
    new Lazy<>(
        () ->
            scheduled(
                Executors.newCachedThreadPool(
                    factory("Firebase Blocking", ...))));

On low-end Android devices (2GB RAM, 2 CPU cores), this creates 68+ "Firebase Blocking Thread" instances during normal app usage. Each thread consumes ~1MB of stack space, contributing to memory pressure that triggers the low memory killer (lmkd).

Environment

  • Firebase product: firebase-messaging 25.0.1 (only Firebase product in use; BLOCKING_EXECUTOR comes from the transitive firebase-common dependency)
  • Device: 2GB RAM / 2-core CPU (emulator configured to simulate low-end Android Go devices)
  • Android version: API 34

Evidence

Thread dump captured via Thread.getAllStackTraces() shows 68 threads named "Firebase Blocking Thread #0" through "Firebase Blocking Thread #67", all in TIMED_WAITING state:

"Firebase Blocking Thread #0"  TIMED_WAITING
"Firebase Blocking Thread #1"  TIMED_WAITING
...
"Firebase Blocking Thread #67" TIMED_WAITING

Shortly after, the process was killed by lmkd:

lowmemorykiller: reason: low watermark is breached and swap is low (276kB < 151960kB)
ActivityManager: Process has died: fg  TOP

The app was the foreground TOP process and was still killed due to system-wide memory exhaustion.

Comparison

On a higher-spec emulator (4+ cores, more RAM), the same executor creates ~37 "Firebase Blocking Thread" instances. The slower CPU on the 2GB device means tasks take longer to complete, so more threads pile up before they can be reused — the exact scenario where unbounded pools are most dangerous.

Suggested fix

Replace Executors.newCachedThreadPool() with a bounded ThreadPoolExecutor in ExecutorsRegistrar.BLOCKING_EXECUTOR:

static final Lazy<ScheduledExecutorService> BLOCKING_EXECUTOR =
    new Lazy<>(
        () ->
            scheduled(
                new ThreadPoolExecutor(
                    0,
                    Math.max(4, Runtime.getRuntime().availableProcessors()),
                    60L, TimeUnit.SECONDS,
                    new SynchronousQueue<>(),
                    factory("Firebase Blocking", ...))));

This caps thread creation while preserving the same idle-thread reaping behavior. A LinkedBlockingQueue alternative would allow tasks to queue safely when all threads are busy.

主要言語
Java
スター
2.6k
フォーク
733
平均マージ
2日 15時間
マージ済み PR(30日)
72

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

firebase/firebase-android-sdk のほかの issue

firebase/firebase-android-sdk の issue をすべて見る

似ている issue

Java の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。