Description
A native SIGSEGV (fault addr 0x0) crashes the whole app on the JS thread itself (mqt_v_js), reproducible under rapid AudioBufferSourceNode creation/teardown churn where each node has a real onEnded listener attached. The crash happens inside the JS thread's own event loop / microtask checkpoint machinery - the backtrace shows no react-native-audio-api frames at all, which suggests the actual corruption happens earlier and the crash only surfaces later when Hermes touches the (already-corrupted) heap during its own bookkeeping.
Expected: no crash, regardless of how quickly source nodes are churned.
Crash signature:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid <N> (mqt_v_js), pid <N> (com.audioapileakrepro)
Backtrace (trimmed, full untrimmed logcat available on request):
#00-#18 libhermesvm.so (various offsets, no exported symbols)
#19 facebook::react::RuntimeScheduler_Modern::performMicrotaskCheckpoint(facebook::jsi::Runtime&)+140
#20 facebook::react::RuntimeScheduler_Modern::runEventLoopTick(facebook::jsi::Runtime&, facebook::react::Task&)+220
#21 facebook::react::RuntimeScheduler_Modern::runEventLoop(facebook::jsi::Runtime&)+140
#22-#40 libreactnative.so (various offsets)
#41 facebook::jni::...JNativeRunnable::run()...
#42 facebook::jni::...FunctionWrapper...call(...)
#43 art_jni_trampoline
i.e. the crash surfaces while the JS thread is running its own RuntimeScheduler event loop, processing a runnable that was posted onto it via JNI (the CallInvoker/invokeAsync path).
Also seen in production, not just synthetic churn: the same signature (SIGSEGV, mqt_v_js, fault addr 0x0) has occurred intermittently (~5 times across a multi-hour session) in a separate real app doing ordinary rapid track-switching during audio playback (React Native 0.85.0), so this isn't purely a synthetic-stress-test artifact - though the synthetic repro below is far more reliable for actually catching it.
A ruled-out hypothesis, in case it saves someone time: given the crash's relationship to rapid source-node churn with an attached event listener, I suspected AudioEventHandlerRegistry::unregisterHandler() releasing its last shared_ptr<jsi::Function> reference off the JS thread - it's called from ~EventCaller(), which runs on whatever thread destroys the owning node, and AudioGraphManager's nodeDestructor_ (AudioDestructor) tears nodes down on a dedicated background thread rather than the JS thread. I patched unregisterHandler() to extract the handler under the existing lock and post its actual release onto the JS thread via callInvoker_->invokeAsync(...) instead of letting it destruct in place. This did not fix the crash - rebuilt with that patch applied (confirmed via .so timestamps postdating the source edit), reran the identical repro, and got the same signature/backtrace shape. So either the corruption comes from somewhere else entirely, or there's another cross-thread release of a JSI object I haven't found (e.g. AudioEventHandlerRegistry's own destructor eventHandlers_.clear(), or something outside this file).
I did create and verify the MRE below before filing this (built and ran it, reproduced the crash on both the unpatched library and with the ruled-out patch applied).
Steps to reproduce
- Clone the repro repo,
npm install, npx react-native run-android
- Tap "Create AudioContext"
- Tap "Run 4000 onended churn (SIGSEGV repro)" (or the "crossfade-style overlap churn" button for a variant closer to a real crossfade - two distinct
AudioBuffers, genuine node overlap instead of strict stop-then-create, background GC pressure)
- Watch
adb logcat for Fatal signal 11 - it's a race, not deterministic, so it may take a few runs (observed anywhere from the first run to a handful of runs) to land
Each iteration: creates a fresh AudioBufferSourceNode + GainNode pair wrapping an AudioBuffer (silent, prebuilt once), registers a real source.onEnded = () => {} listener, starts it, waits a short interval, then stop()s and disconnect()s the previous pair.
Snack or a link to a repository
https://github.com/WentTheFox/AudioApiLeakRepro
(A separate, smaller repo originally built for #1263 - this is a distinct bug from that one, unrelated to the AudioBuffer copy-caching fix in #1281/#1283.)
React Native Audio API version
0.13.3
React Native version
0.87.0 (repro app) / 0.85.0 (the separate production app mentioned above)
Platforms
Android
JavaScript runtime
Hermes
Workflow
React Native
Architecture
Fabric (New Architecture)
Build type
Debug app & dev bundle
Device
Real device
Device model
Samsung Galaxy S24+ (SM-S926B), Android 16
Acknowledgements
Yes
Description
A native
SIGSEGV(fault addr0x0) crashes the whole app on the JS thread itself (mqt_v_js), reproducible under rapidAudioBufferSourceNodecreation/teardown churn where each node has a realonEndedlistener attached. The crash happens inside the JS thread's own event loop / microtask checkpoint machinery - the backtrace shows noreact-native-audio-apiframes at all, which suggests the actual corruption happens earlier and the crash only surfaces later when Hermes touches the (already-corrupted) heap during its own bookkeeping.Expected: no crash, regardless of how quickly source nodes are churned.
Crash signature:
Backtrace (trimmed, full untrimmed logcat available on request):
i.e. the crash surfaces while the JS thread is running its own
RuntimeSchedulerevent loop, processing a runnable that was posted onto it via JNI (theCallInvoker/invokeAsyncpath).Also seen in production, not just synthetic churn: the same signature (
SIGSEGV,mqt_v_js, fault addr0x0) has occurred intermittently (~5 times across a multi-hour session) in a separate real app doing ordinary rapid track-switching during audio playback (React Native 0.85.0), so this isn't purely a synthetic-stress-test artifact - though the synthetic repro below is far more reliable for actually catching it.A ruled-out hypothesis, in case it saves someone time: given the crash's relationship to rapid source-node churn with an attached event listener, I suspected
AudioEventHandlerRegistry::unregisterHandler()releasing its lastshared_ptr<jsi::Function>reference off the JS thread - it's called from~EventCaller(), which runs on whatever thread destroys the owning node, andAudioGraphManager'snodeDestructor_(AudioDestructor) tears nodes down on a dedicated background thread rather than the JS thread. I patchedunregisterHandler()to extract the handler under the existing lock and post its actual release onto the JS thread viacallInvoker_->invokeAsync(...)instead of letting it destruct in place. This did not fix the crash - rebuilt with that patch applied (confirmed via.sotimestamps postdating the source edit), reran the identical repro, and got the same signature/backtrace shape. So either the corruption comes from somewhere else entirely, or there's another cross-thread release of a JSI object I haven't found (e.g.AudioEventHandlerRegistry's own destructoreventHandlers_.clear(), or something outside this file).I did create and verify the MRE below before filing this (built and ran it, reproduced the crash on both the unpatched library and with the ruled-out patch applied).
Steps to reproduce
npm install,npx react-native run-androidAudioBuffers, genuine node overlap instead of strict stop-then-create, background GC pressure)adb logcatforFatal signal 11- it's a race, not deterministic, so it may take a few runs (observed anywhere from the first run to a handful of runs) to landEach iteration: creates a fresh
AudioBufferSourceNode+GainNodepair wrapping anAudioBuffer(silent, prebuilt once), registers a realsource.onEnded = () => {}listener, starts it, waits a short interval, thenstop()s anddisconnect()s the previous pair.Snack or a link to a repository
https://github.com/WentTheFox/AudioApiLeakRepro
(A separate, smaller repo originally built for #1263 - this is a distinct bug from that one, unrelated to the
AudioBuffercopy-caching fix in #1281/#1283.)React Native Audio API version
0.13.3
React Native version
0.87.0 (repro app) / 0.85.0 (the separate production app mentioned above)
Platforms
Android
JavaScript runtime
Hermes
Workflow
React Native
Architecture
Fabric (New Architecture)
Build type
Debug app & dev bundle
Device
Real device
Device model
Samsung Galaxy S24+ (SM-S926B), Android 16
Acknowledgements
Yes