Skip to content

SIGSEGV in ~CallbackWrapper when a TurboModule callback is destroyed off the JS thread during runtime teardown #59578

Open

Description

@l2hyunwoo

Description

SyncCallback's destructor (and so AsyncCallback's, which owns one) can run on any thread. It calls wrapper_.lock(), so for a short time that thread holds a strong reference to the CallbackWrapper:

https://github.com/facebook/react-native/blob/7225d43c34345b8e63c1c72de184e80a2243c9e7/packages/react-native/ReactCommon/react/bridging/Function.h#L127-L132

destroy() removes the collection's reference, so that local strong reference is always the last one, and ~CallbackWrapper runs ~jsi::Function on the background thread. If the runtime is torn down in that window (~TurboModuleBinding clears the LongLivedObjectCollection, then Hermes frees the memory behind the jsi::PointerValue), the result is a use-after-free in PointerValue::invalidate().

This breaks the contract in LongLivedObject.h: "ensure you only hold weak references to the object outside the JS thread to avoid accessing deallocated values when the JS VM is shutdown."

We see this in production on Android (React Native 0.84, Hermes V1, New Architecture / bridgeless). It is a native SIGSEGV on the fbjni HybridData destructor thread, right after the user closes a React Native screen and its ReactHost / runtime is destroyed. The code is the same on main (checked at 7225d43).

The race on Android:

  1. A TurboModule method takes a callback or a promise. createJavaCallback (JavaTurboModule.cpp) wraps an AsyncCallback<> in a JCxxCallbackImpl. For promises, the callback that is not called (for example reject after resolve) stays alive until the Java object is collected. Legacy ReactContextBaseJavaModule modules go through the same function in bridgeless mode.
  2. The Java Callback object is garbage collected. fbjni destroys the C++ JCxxCallbackImpl on its HybridData destructor thread, which runs ~AsyncCallback → ~SyncCallback → wrapper_.lock() → destroy().
  3. At the same time, the React host thread destroys the instance: ~ReactInstance → ~HermesRuntime → finalizers → ~TurboModuleBinding → LongLivedObjectCollection::clear(). Then Hermes deletes the chunks that hold the PointerValues.
  4. The destructor thread releases its strong reference → ~CallbackWrapper → ~jsi::Function → ~Pointer → ptr_->invalidate() on freed memory.

Steps to reproduce

Use the Android reproducer app linked below (React Native 0.84.0 template, release build, no React Native patches):

  1. A native module receives 20,000 JS callbacks and keeps them without calling them.
  2. After 500 ms, the module drops them and forces GC, so fbjni destroys the JCxxCallbackImpls on its HybridData destructor thread.
  3. At the same moment it calls ReactHost.reload(), so the runtime is torn down while those destructors run. Busy threads (2 × CPU cores) keep the CPU loaded, so the destructor thread gets preempted.
  4. The app repeats this about once per second and shows the cycle count.

On a Galaxy S26 Ultra (Android 17), stock 0.84.0 crashed in 4 of 7 runs of 150 s: 2 of 3 with the prebuilt AAR, 2 of 4 with React Native built from source. Each crash was a SIGSEGV on the HybridData Dest thread, 6 to 11 s after launch, with the stack below. With the fix from the linked PR (built from source), 0 of 8 runs crashed. Each of those runs completed 86 to 108 cycles in 150 s.

React Native Version

0.84.0 (also present on main at 7225d43)

Affected Platforms

Runtime - Android

Output of npx @react-native-community/cli info

System:
  OS: macOS 27.0.1
  CPU: (18) arm64 Apple M5 Max
Binaries:
  Node: 22.16.0
  npm: 10.9.2
Languages:
  Java: 17.0.19
npmPackages:
  "@react-native-community/cli": 20.1.0
  react: 19.2.3
  react-native: 0.84.0

Stacktrace or Logs

Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid ... (HybridData Dest)

facebook::jsi::Pointer::~Pointer()                      jsi/jsi.h:672                 ptr_->invalidate()
facebook::jsi::Object::~Object()                        jsi/jsi.h
facebook::jsi::Function::~Function()                    jsi/jsi.h
facebook::react::CallbackWrapper::~CallbackWrapper()    react/bridging/CallbackWrapper.h:21   (implicit)
facebook::react::SyncCallback<void()>::~SyncCallback()  react/bridging/Function.h:127         end of wrapper_.lock() scope
facebook::react::AsyncCallback<>::~AsyncCallback()      react/bridging/Function.h:22
createJavaCallback(...) lambda destructor               react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp:123
facebook::react::JCxxCallbackImpl::~JCxxCallbackImpl()  ReactAndroid/src/main/jni/react/jni/JCallback.h:25
libfbjni.so
com.facebook.jni.HybridData$Destructor.destruct
com.facebook.jni.DestructorThread$1.run


This is the symbolized frame chain, innermost first, from the reproducer app. Frames #00 to #03 are in `libreactnative.so` (Build ID `7e30e03647a3148904c2390445561c90f979a562`, the prebuilt 0.84.0 AAR). We symbolized them with `ndk-stack` and the unstripped prefab copy of `libreactnative.so` from the same AAR. Inlined std frames are not shown. The production crash has the same chain.

MANDATORY Reproducer

https://github.com/l2hyunwoo/react-native-sync-callback-repro

Screenshots and Videos

repro-base.mp4

Stock 0.84.0 (prebuilt AAR): the process dies during cycle 65, 69 s after launch (SIGSEGV on HybridData Dest). The video ends at the crash.

repro-fix.mp4

With the fix (built from source): about 120 cycles over 170 s without a crash.

Activity

  1. react-native-bot commented on Oct 28, 2026

    @react-native-bot
    Collaborator

    Warning

    Unsupported version: It looks like your issue or the example you provided uses an unsupported version of React Native.

    Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please upgrade to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on StackOverflow to get further community support.

  2. react-native-bot commented on Oct 28, 2026

    @react-native-bot
    Collaborator

    Warning

    Unsupported version: It looks like your issue or the example you provided uses an unsupported version of React Native.

    Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please upgrade to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on StackOverflow to get further community support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions