I'm not sure if I'm supposed to file this issue here or in rive-android, because it seems other platforms (ios, web) use the same thread for data binding and rendering - i don't hit the same issue in rive-react in browser.
rive-react-native on android platform uses data binding from 2 different threads and it seems like rive-runtime doesn't expect DataBindContainer to be used from multiple threads.
rive::DataBindContainer is accessed concurrently from two threads with no synchronization. It uses a double-buffering with m_isProcessing flag to handle reentrant calls, the vectors are not protected by any mutex. When the JS thread calls property.set() while the Rive render thread is inside updateDataBinds(), the result is memory corruption and a SIGSEGV that terminates the process.
I wonder if it's supposed to be used from multiple threads like rive-android does?
Would it be possible to fix this in rive-runtime by allowing multi threaded, or should i go to rive-android team to ask them to figure out how to utilize it on single thread / mutual lock these jobs?
- rive-android -
JNIRenderer::doFramem() is called from the Android Choreographer and immediately dispatches all advance/draw work onto a WorkerThread. updateDataBinds runs on that thread while property.set() runs on mqt_v_js. Two separate OS threads, no lock.
- rive-ios -
CADisplayLink is added to .main RunLoop. tick()-> advanceAndApply() -> updateDataBinds() all fire on the main thread. React Native JS on iOS also runs on the main thread.
- @rive-app/react-webgl2 - single-threaded JS runtime. Advance and writes happen in the same event loop turn, all good.
Android deliberately offloads rendering to a background thread for GPU pipeline performance, and that is the only reason the missing mutex in DataBindContainer becomes a crash.
Here are partial stack traces we hit if we update rive bindings frequently:
Crash 1 — JS thread, addDirtyDataBind (high-frequency updates, ~34s uptime)
Fatal signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7aa81a617fe8
in tid 5305 (mqt_v_js)
#00 librive-android.so rive::DataBindContainer::addDirtyDataBind(rive::DataBind*)+352
#01 librive-android.so rive::ViewModelInstanceNumber::propertyValueChanged()+49
#02 libart.so art_quick_generic_jni_trampoline
...
#26 [nitro jit] com.margelo.nitro.rive.HybridViewModelNumberProperty.set
...
#63 libreactnative.so facebook::react::RuntimeScheduler_Modern::performMicrotaskCheckpoint
#64 libreactnative.so facebook::react::RuntimeScheduler_Modern::runEventLoopTick
Crash 2 — JS thread, addDirtyDataBind
Fatal signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7aa7da619fe0
in tid 6136 (mqt_v_js)
#00 librive-android.so rive::DataBindContainer::addDirtyDataBind(rive::DataBind*)+352
#01 librive-android.so rive::ViewModelInstanceNumber::propertyValueChanged()+49
...
#18 [jit] app.rive.runtime.kotlin.core.ViewModelProperty.setValue+153
...
#24 [nitro jit] com.margelo.nitro.rive.HybridViewModelNumberProperty.set
Crash 3 — Rive render thread, updateDataBinds (10 Hz property updates, ~474s uptime)
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0000000100000029
in tid 6736 (Thread-7)
registers:
r14 0000000100000000 ← corrupted vector data pointer
#00 librive-android.so rive::DataBindContainer::updateDataBinds(bool)+373
#01 librive-android.so rive::StateMachineInstance::tryChangeState()+28
#02 librive-android.so rive::NestedArtboard::advanceComponent(float, rive::AdvanceFlags)+580
#03 librive-android.so rive::NestedArtboard::advanceComponent(float, rive::AdvanceFlags)+681
#04 librive-android.so rive::StateMachineInstance::advanceAndApply(float)+399
#05 librive-android.so Java_app_rive_runtime_kotlin_core_StateMachineInstance_cppAdvance
...
#08 [jit] app.rive.runtime.kotlin.controllers.RiveFileController.resolveStateMachineAdvance
#09 [jit] app.rive.runtime.kotlin.controllers.RiveFileController.advance
#10 [jit] app.rive.runtime.kotlin.renderers.RiveArtboardRenderer.advance
...
#18 librive-android.so rive_android::WorkerImpl::doFrame(...)
#20 librive-android.so rive_android::WorkerThread::threadMain()
I'm not sure if I'm supposed to file this issue here or in rive-android, because it seems other platforms (ios, web) use the same thread for data binding and rendering - i don't hit the same issue in rive-react in browser.
rive-react-nativeon android platform uses data binding from 2 different threads and it seems likerive-runtimedoesn't expectDataBindContainerto be used from multiple threads.rive::DataBindContaineris accessed concurrently from two threads with no synchronization. It uses a double-buffering withm_isProcessingflag to handle reentrant calls, the vectors are not protected by any mutex. When the JS thread callsproperty.set()while the Rive render thread is insideupdateDataBinds(), the result is memory corruption and a SIGSEGV that terminates the process.I wonder if it's supposed to be used from multiple threads like rive-android does?
Would it be possible to fix this in rive-runtime by allowing multi threaded, or should i go to rive-android team to ask them to figure out how to utilize it on single thread / mutual lock these jobs?
JNIRenderer::doFramem()is called from the Android Choreographer and immediately dispatches all advance/draw work onto aWorkerThread.updateDataBindsruns on that thread whileproperty.set()runs onmqt_v_js. Two separate OS threads, no lock.CADisplayLinkis added to.mainRunLoop.tick()->advanceAndApply()->updateDataBinds()all fire on the main thread. React Native JS on iOS also runs on the main thread.Android deliberately offloads rendering to a background thread for GPU pipeline performance, and that is the only reason the missing mutex in
DataBindContainerbecomes a crash.Here are partial stack traces we hit if we update rive bindings frequently:
Crash 1 — JS thread,
addDirtyDataBind(high-frequency updates, ~34s uptime)Crash 2 — JS thread,
addDirtyDataBindCrash 3 — Rive render thread,
updateDataBinds(10 Hz property updates, ~474s uptime)