Skip to content

fix(android): dispatch Rive events on the UI thread to avoid Reanimated deadlock/ANR - #445

Open
Togetic wants to merge 1 commit into
rive-app:mainfrom
Togetic:fix/android-dispatch-events-on-ui-thread
Open

fix(android): dispatch Rive events on the UI thread to avoid Reanimated deadlock/ANR#445
Togetic wants to merge 1 commit into
rive-app:mainfrom
Togetic:fix/android-dispatch-events-on-ui-thread

Conversation

@Togetic

@Togetic Togetic commented Sep 1, 2026

Copy link
Copy Markdown

Fixes #444.

Problem

RiveReactNativeView emits all of its JS events from whatever thread the Rive runtime calls it on:

reactContext.getJSModule(RCTEventEmitter::class.java)
  .receiveEvent(id, Events.PLAY.toString(), data)

Rive's render thread runs these callbacks while holding the state-machine advance lock. With Reanimated in the tree the event's delivery is routed through the main thread, so when the main thread is itself waiting on that lock the two threads deadlock — a lock-ordering inversion that surfaces as an ANR. See #444 for the full description.

Change

Wrap the seven dispatch sites in UiThreadUtil.runOnUiThread { … } so the emitting thread hands the event off instead of blocking inside the bridge call while holding Rive's lock:

  • onPlay, onPause, onStop, onLoopEnd, onStateChanged, onRiveEventReceived, and the sendErrorToRN error path.

Mechanical change — one new import, no change to event names, payloads, or relative ordering. Delivery simply starts from the UI thread, which is also the usual expectation for RCTEventEmitter.

Testing — please read

I want to be straight about this rather than imply more verification than I did:

  • Running in production: we ship this exact change as a local patch over 9.8.0 in a React Native 0.86.2 / Expo 57 app on Fabric, and the ANR stopped.
  • Not built from this branch: I rebased the change onto current main and have not compiled main or run the example app locally, so please treat CI and your own review as the real gate. The diff is small enough to read in full, and main's call sites are byte-identical to the ones we patch.
  • No minimal repro: reproducing needs Fabric plus Reanimated plus a Rive state machine emitting events under load. Happy to attempt one if you want it before merging.

I'm aware this is the legacy runtime and @rive-app/react-native is where things are heading — offering this under the README's "medium term: address major concerns in this legacy package while supporting migration", since an ANR is painful for apps mid-migration. Entirely understand if you'd rather fix it differently or not at all.

Rive's render thread emitted JS events inline while holding the
state-machine advance lock. With Reanimated in the tree, delivery is
routed through the main thread, so if the main thread was waiting on
that same lock the two threads deadlocked and Android raised an ANR.

Hand each event to the UI thread via UiThreadUtil.runOnUiThread so the
emitting thread never holds the Rive lock across the bridge call.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Rive events dispatched off the UI thread can deadlock with Reanimated under Fabric (ANR)

1 participant