Skip to content

Data binding thread safety #107

@timadevelop

Description

@timadevelop

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()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions