Skip to content

🐛 [Android] Each FrameOutput leaks its native thread (dispose() doesn't shut down the executor; useFrameOutput never disposes) #4213

Open

Description

@dmandrade

Prerequisites

Reproduction

#4212

Steps to reproduce

  1. install simple app on an Android device or emulator
  2. Open the camera screen (uses useFrameOutput with a trivial onFrame that disposes the frame)
  3. Toggle the camera off and on
  4. After each cycle run: adb shell ps -T -p $(adb shell pidof ) | grep -c camera.frame

What did you expect to happen?

Closing the camera frees its frame thread. The count stays at 1 while the camera is open and 0 when it's closed.

What actually happened?

The count goes up by one every time the camera opens and never goes down. In our production app, in a test session reached 42 threads, and memory kept growing with each camera open. There's no crash; the app gets slower until it freezes.

Affected platforms

Android (device)

Device(s) affected

Android, Samsung s23e, Samsung galaxy AO5 and A07, Motorola G35

VisionCamera version

5.1.1

React Native version

0.86.3

React Native architecture

New Architecture (Fabric / bridgeless)

Features being used

  • Preview
  • Photo capture
  • Video capture
  • Frame Processors (worklets)
  • Skia Frame Processors
  • Code/Barcode Scanner
  • Location metadata
  • Multi-cam
  • Depth data
  • HDR / custom dynamic range
  • Custom format / FPS / resolution

Relevant logs / stack trace

# VisionCamera 5.1.1, Android 14 emulator (API 34, x86_64)
# logs captured after the app froze following repeated camera screen opens.
# No error or warning is logged by VisionCamera/CameraX; the leak is silent in logcat.

total threads: 235, "lo.camera.frame" threads: 42 (all S / futex_wait)

16143 S 27465 futex_wait com.pdvmundo
16618 S 56 futex_wait lo.camera.frame
16784 S 84 futex_wait lo.camera.frame
16956 S 123 futex_wait lo.camera.frame
17063 S 139 futex_wait lo.camera.frame
17153 S 111 futex_wait lo.camera.frame
17260 S 115 futex_wait lo.camera.frame
...  (36 more, same state)
21752 S 88 futex_wait lo.camera.frame
21918 S 103 futex_wait lo.camera.frame

Additional context

No response

Submission

  • The reproduction I linked is either (preferred) a PR against this repo that adds a failing harness test following the harness-tests README, or (fallback) a public repo that reproduces the bug on a fresh clone. I understand the issue will be closed without one.
  • I pasted logs as text (not screenshots).
  • I wrote this report in my own words. I did not paste AI-generated descriptions of the bug.

Activity

  1. chocky335 commented on Oct 19, 2026

    @chocky335

    I have a fix for this on Android in #4227. The frame thread now exits after 5 s without a frame callback, and useFrameOutput removes its callbacks on unmount. On a Pixel 8 Pro, 10 mount and unmount cycles left 10 camera.frame threads before and 0 after a 7 s hold. The PR includes the #4212 test.

  2. chocky335 commented on Oct 20, 2026

    @chocky335

    @dmandrade, since your app reached 42 leaked threads: could you try the branch from #4227 (chocky335:android/frame-output-executor-leak) and watch adb shell ps -T -p <pid> | grep camera.frame after a few screen changes? With the branch, that count returns to zero about 5 s after the last frame processor stops. If it stays up in your app, the logcat around that moment would help me.

  3. dmandrade commented on Oct 20, 2026

    @dmandrade
    Author

    Sure @chocky335! I'll test our app against the #4227 branch, repeat the same flow, and report back.

  4. guuhgalvao commented on Oct 29, 2026

    @guuhgalvao

    We ported PR #4227 (890f5da) onto react-native-vision-camera@5.1.1 + react-native-vision-camera-worklets@5.1.1 with patch-package (staying off 5.2.x because of Nitro 0.37).

    Ran the ps -T protocol you asked for on a Pixel_API_36 emulator, same process (com.pdvmundo pid 23191) across 10 camera open/close cycles:

    adb shell ps -T -p 23191 | grep camera.frame
    

    | cycle | while open | ~1 s after close | ~7 s after close | thread id |
    |------:|-----------:|-----------------:|-----------------:----------:|
    | 1 | 1 | 1 | 0 | 23362 → (gone) |
    | 2 | 1 | 1 | 0 | 23572 |
    | 3 | 1 | 1 | 1 (still R) | 23660 |
    | 4 | 1 | 1 | 0 | 23660 (exited) |
    | 6 | 1 | 1 | 0 | 24831 |
    | 7 | 1 | 1 | 0 | 24929 |
    | 8 | 1 | 1 | 0 | 25022 |
    | 9 | 1 | 1 | 0 | 25116 |
    | 10 | 1 | 1 | 0 | 25214 → 25307 on reopen, then 0 |

    Count never grew 1, 2, 3… A new camera.frame thread appears per session and the previous one is gone. After the last close, count is 0. Pid stayed 23191 the whole time (no crash / process restart).

    Cycle 3 still had 1 thread at +7 s (state R); the next idle window it dropped to 0 — delayed idle, not a leak. No logcat captured at that instant because the following cycle already showed 0.

    Photo smoke on the same process: first shutter did capture (PHOTO_ORIENT capture_raw, 1856×1392), several captures in a row, no ANR / FATAL / process death. PSS still ratchets (~835 MB → ~1.07 GB over extra sessions; ~1.09 → 1.23 GB across the capture burst) — that is a separate native-memory issue, not this thread leak.

    We do not have the <4 GB Samsung in hand for this run; we will repeat ps -T there when we do.

  5. guuhgalvao commented on Oct 30, 2026

    @guuhgalvao

    Follow-up on a 3 GB emulator cold boot (Pixel_API_36, hw.ramSize=3G, MemTotal 3047 MB), same patched 5.1.1 build:

    ps -T | grep camera.frame over 10 open/close cycles in pid 4580: 1 while open, 1 just after close, 0 at ~7 s. Thread ids changed each session; count never stacked.

    PSS still ratcheted: 761 MB (idle) → 922 MB (camera open) → 1304 MB after 10 sessions; MemAvailable 995 → 648 MB.

    Photo smoke: first two shutters captured (capture_raw). The process then disappeared (pid gone, JS dropped). The emulator itself stayed up; MemAvailable recovered to ~1.69 GB. So the thread leak looks fixed even under 3 GB; the remaining kill is the native PSS ratchet, not leftover camera.frame threads.

  6. chocky335 commented on Nov 3, 2026

    @chocky335

    Thanks @guuhgalvao, this is exactly the check I needed: one new camera.frame thread per session and none left after close, also on 3 GB. The PSS growth you see is outside this fix. #4227 only changes the thread lifetime, and a process kill with no camera.frame threads left points at native memory that is not freed. A separate issue with a heap profile (adb shell dumpsys meminfo <pid> across sessions, or a Perfetto native heap trace) would give @mrousavy something to work with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions