Skip to content

[iOS Crash] EXC_BAD_ACCESS in ReanimatedModuleProxy::performOperations during AnimationFrameBatchinator::flush (RN 0.85.1 + Reanimated 4.3.0 + worklets 0.8.1, New Architecture) #9293

@stage88

Description

@stage88

Description

Production crash on iOS: EXC_BAD_ACCESS (KERN_INVALID_ADDRESS) at
-[REANodesManager performOperations] (REANodesManager.mm:127, which
is the _performOperations(); call that invokes
ReanimatedModuleProxy::performOperations() via the block registered
in NativeProxy.mm:47-51).

The crash is triggered on the main thread from the rAF path:

QuartzCore DisplayLink
  -> -[AnimationFrameQueue executeQueue:]
  -> worklets::AnimationFrameBatchinator::flush()  (line 50 in
     AnimationFrameBatchinator.cpp, inside the `runSync` loop)
  -> WorkletRuntime::runSync<double&>(timestamp)
  -> WithRuntimeDecorator<worklets::AroundLock>::call(...)
  -> Hermes interprets JS -> Array.prototype.forEach
  -> reanimated host function (createHostFunction)
  -> -[REANodesManager performOperations]   <-- crash

At the same time, the JS thread is blocked on the same
AroundLock/recursive_mutex inside
WorkletRuntime::runSyncSerialized, i.e. a concurrent runOnUISync
call was waiting for the UI runtime lock the main thread held.

This matches the race flagged by the comment in
ReanimatedModuleProxy.cpp:729:

It may happen that performOperations is called on the UI thread
while React Native tries to commit a new tree on the JS thread.

We believe the crash is a null/stale dereference inside
ReanimatedModuleProxy::performOperations() (likely uiManager_ or
the shadow tree for a surface that is being torn down) during the
window described in that comment.

This issue is similar in spirit to #9111 (React commits blocking
reanimated updates), but manifests as a hard crash instead of a
freeze. It may also relate to #6245 (Race condition in
NativeReanimatedModule::performOperations).

Impact

42 unique users, 43 events in the first hours after the release that
upgraded to this stack. Crash rate is trending up. Tagged "Early
crashes" in Crashlytics, i.e. the crash happens soon after app
launch. No such crashes in the previous release on RN 0.84 +
Reanimated 3.x. The only change in the crashing release is the
upgrade to RN 0.85.1 + Reanimated 4.3.0 + worklets 0.8.1.

Steps to reproduce

No deterministic repro; the crash is aggregated from production
telemetry. Given the "Early crashes" tag and the observed pattern
(main thread inside AroundLock + JS thread waiting on the same lock),
the trigger is likely a rapid surface mount/unmount early in app
start (e.g. navigation transition or reanimated-heavy component
mount) racing with the rAF flush. Reanimated is used transitively via
common libraries (react-native-reanimated-carousel,
react-native-bottom-tabs, @gorhom/bottom-sheet,
react-native-sortables, react-native-screens).

Snack or a link to a repository

Not available - production crash aggregated via Crashlytics, no
minimal repro yet. Filing anyway per the guidance in #6245 that a
clear explanation of the race can still be actionable.

Main-thread stack (symbolicated, app-specific frames removed)

0  -[REANodesManager performOperations] + 127 (REANodesManager.mm:127)
1  reanimated::jsi_utils::createHostFunction<...> lambda (jsi.h:1754)
2  std::function<jsi::Value(...)>::operator() (function.h:772)
3  std::function<jsi::Value(...)>::operator() (function.h:772)
4  hermesvm std::function<...>::operator()
5  HermesRuntimeImpl::HFContext::func
6  hermes::vm::NativeFunction::_nativeCall
7  hermes::vm::Interpreter::handleCallSlowPath
8  hermes::vm::Interpreter::interpretFunction<false,false>
9  hermes::vm::Runtime::interpretFunctionImpl
10 hermes::vm::JSFunction::_callImpl
11 hermes::vm::Callable::executeCall3
12 hermes::vm::arrayPrototypeForEach
13 hermes::vm::NativeFunction::_nativeCall
14 hermes::vm::Interpreter::handleCallSlowPath
15 hermes::vm::Interpreter::interpretFunction<false,false>
16 hermes::vm::Runtime::interpretFunctionImpl
17 hermes::vm::JSFunction::_callImpl
18 HermesRuntimeImpl::call
19 jsi::WithRuntimeDecorator<worklets::AroundLock, ...>::call
20 worklets::WorkletRuntime::runSync<double&> (jsi-inl.h:313)
21 worklets::AnimationFrameBatchinator::flush()::$_0
   (AnimationFrameBatchinator.cpp:50)
22 -[AnimationFrameQueue executeQueue:]
23 CA::Display::DisplayLinkItem::dispatch_
24 CA::Display::DisplayLink::dispatch_items
25 CA::Display::DisplayLink::dispatch_deferred_display_links
26-37 UIKit / UpdateCycle / CFRunLoop / UIApplicationMain
38 main

JS thread at time of crash (blocked on same lock)

0  __psynch_mutexwait
1  _pthread_mutex_firstfit_lock_wait
2  _pthread_mutex_firstfit_lock_slow
3  std::recursive_mutex::lock()
4  worklets::WorkletRuntime::runSyncSerialized (WorkletRuntime.h:101)
5  worklets::runOnUISync(weak_ptr<WorkletRuntime>, jsi::Runtime&,
   jsi::Value const&)
6  Hermes interpreter frames
...
(JS dispatched a runOnUISync call, which is waiting on the same
AroundLock the main thread holds while inside performOperations.)

Reanimated version

4.3.0

Worklets version

0.8.1

React Native version

0.85.1

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native CLI

Architecture

New Architecture (Fabric renderer)

Reanimated feature flags

No

React Native release level

Stable

Build type

Release app & production bundle

Device

Real device

Host machine

macOS

Device model

Multiple models (42 distinct users affected); crash is not device-
specific.

Acknowledgements

Yes - searched for similar issues. Closest matches: #9111, #6245,
#7666. Filing a dedicated thread because none of those describe a
reproducible null-deref crash under the 4.3.0 + worklets 0.8.1 + RN
0.85.1 stack, and the crash rate is high enough to warrant tracking
separately.


generated with Claude

Metadata

Metadata

Assignees

Labels

Missing reproThis issue need minimum repro scenarioPlatform: iOSThis issue is specific to iOS

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions