software-mansion / software-mansion/react-native-audio-api

[Android] Repeated seek (recreate source node, same AudioBuffer) leaks native heap - never released by disconnect(), crashes via Hermes GC OOM

Open
#1,263 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

android bug pkg:audio-api
Dominant language
C++
Stars
839
Forks
92
Avg merge
4d 15h
Merged PRs (30d)
27

Description

Description

Repeatedly creating, starting, stopping, and disconnecting AudioBufferSourceNode/GainNode pairs that all wrap the same, already-built AudioBuffer (the pattern required to reposition a one-shot source node - there's no seek() on a live AudioBufferSourceNode, in this library or any browser) leaks native heap. disconnect() does not release it.

A minimal repro app running 60 such "seeks" (~150ms apart, roughly human tapping speed, not synthetic max-speed spam) took resident memory from ~369 MB to 4637 MB - about 71 MB leaked per call, suspiciously close to the full buffer's own size (240s × 44100Hz × 2ch × 4 bytes ≈ 84.7 MB), which suggests each new source triggers an internal full copy of the PCM data rather than sharing/referencing the existing buffer. dumpsys meminfo confirms the leaked memory sits in Native Heap (Heap Size/Alloc reached ~5.2 GB, ~3.2 GB of it swapped to disk under the resulting pressure) - not Java/Dalvik heap, not graphics. Left running, this eventually crashes the app via a Hermes GC OOM (SIGSEGV in the mqt_v_js thread) once system memory pressure leaves nothing for Hermes to grow into.

I don't believe this is the same bug as #717 (clearOnEndedCallback, fixed in 0.10.0, which we're past) - the crash backtrace here has no react-native-audio-api frames at all (entirely inside libhermesvm.so/libreactnative.so's task scheduler, consistent with heap exhaustion rather than a direct native crash), and the leak itself is independently reproducible via dumpsys//proc/<pid>/status well before any crash occurs.

Originally found in a real app crashing under ordinary repeated seeking on a single track - the linked repro strips that down to a single-file app with no other dependencies.

Steps to reproduce
  1. Clone/run the linked repro
  2. Wait for "Ready." and note the starting RSS shown at the top
  3. Tap "Run 60 rapid seeks"
  4. Watch RSS climb continuously during the run and never drop back down afterward
  5. Repeat the tap a few more times / leave it running longer and the app eventually crashes with a Hermes GC OOM → SIGSEGV in mqt_v_js
Snack or a link to a repository

https://github.com/WentTheFox/AudioApiLeakRepro

React Native Audio API version

0.13.3

React Native version

0.87.0

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native (bare CLI)

Architecture

Fabric (New Architecture)

Build type

Debug app & dev bundle

Device

Real device

Device model

Samsung Galaxy S24+ (SM-S926B), Android 16 (API 36), arm64-v8a

Acknowledgements

Yes

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked AudioApiLeakRepro and reproduce the repeated AudioBufferSourceNode/GainNode lifecycle on Android, measuring RSS and Native Heap with dumpsys meminfo or /proc//status. Trace the Android native handling of repeated sources sharing one AudioBuffer; done means repeated seeks no longer cause unbounded native-heap growth or the reported Hermes GC OOM.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, cpp, javascript
Domain
audio-video-rtc, mobile, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.