Skip to content

jslok/nitro-module-leak

Repository files navigation

vision-camera-leak

Minimum-viable reproduction for a JNI global reference leak in react-native-vision-camera v5 on Android.

Related upstream issues:

What this demonstrates

After several minutes with the camera attached, the process aborts with:

JNI ERROR (app bug): global reference table overflow (max=51200)
... ~50,300 of com.margelo.nitro.camera.HybridFrameSpec$CxxPart

The frame processor body is intentionally empty — just frame.dispose(). This rules out user code as the cause: simply attaching a frame output to the camera at 30+ fps is sufficient.

With heavier worklet workloads, the same root cause manifests as an ART SuspendAll timeout SIGABRT on the camera frame thread instead — both crashes are downstream of the same JHybridObject::CxxPart ↔ Java HybridObject$CxxPart strong-reference cycle.

Versions

react-native-vision-camera          ^5.0.9
react-native-vision-camera-worklets ^5.0.9
react-native-worklets               ^0.8.3
react-native-reanimated             ~4.1.1
react-native-nitro-modules          ^0.35.6
react-native                          0.81.5
expo                                ~54.0.33

Reproduce

npm install
npx expo prebuild --clean
npx expo run:android        # or --device <serial>

Grant the camera permission, leave the app open on the camera view, and wait. Expected time-to-crash by camera FPS:

Camera FPS Time-to-crash
120 ~7 min
60 ~14 min
30 ~28 min

The relationship is linear-inverse-proportional: roughly 1 leaked JNI global ref per camera frame, regardless of FPS or what's in the FP body.

The FPS in App.tsx is set to 120 by default. Lower it for a slower repro.

What to look for

In adb logcat, watch for:

F libc    : Fatal signal 6 (SIGABRT)
F DEBUG   : Abort message: 'JNI ERROR (app bug): global reference table overflow ...'
F DEBUG   :     ##### of com.margelo.nitro.camera.HybridFrameSpec$CxxPart

To watch memory grow before the crash, every minute or so:

adb shell dumpsys meminfo com.vision-camera-leak | grep -E "Other \(malloced\)"

The Other (malloced) count grows linearly with frames processed. At 120fps you should see roughly +14,000 allocations per minute.

Affected configurations

Reported on:

  • Pixel devices (Android 13-16)
  • Samsung A12, Samsung tablet (per upstream issue #3879)
  • OnePlus Nord CE4 Android 16 (per upstream issue #3824)

Both dev and release builds are affected. Build mode only changes time-to-crash by a small factor (release is slightly slower due to better Hermes / GC efficiency, but the bug fundamentally happens regardless).

Mitigations (band-aids, not real fixes)

These linearly extend time-to-crash but don't prevent it:

  • Cooldown after each FP body: forces the camera frame thread idle briefly between dispatches → more time for GC to reclaim Frame objects.
  • Reduce per-frame SharedValue reads: each sv.value access is a worklet-runtime mutex cycle; fewer reads = less Runnable pressure.
  • Lower the camera FPS: directly halves the leak rate.

None of these break the JHybridObject::CxxPart reference cycle. The real fix has to come from react-native-nitro-modules itself.

About

Minimum-viable reproduction for the JNI global reference leak in react-native-vision-camera v5 on Android

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors