Skip to content

Android: a TurboModule's CallInvoker::invokeAsync from a native thread ends the process when Java's heap is full and the JS thread is idle #59995

Open

Description

@YevheniiKotyrlo

Description

On Android, when a C++ TurboModule posts to an idle JS thread through its CallInvoker::invokeAsync (here through AsyncCallback) from a thread of its own while Java's heap is full, the noexcept post reaches a Java call that fails, and the process ends inside it.

The post is then the first task of an empty queue, so RuntimeScheduler_Modern::scheduleTask schedules the event loop from the posting thread, and the runtime executor reaches JMessageQueueThread::runOnQueue, which opens a jni::ThreadScope and calls MessageQueueThreadImpl.runOnQueue with a new NativeRunnable:

  • From a thread the JVM has not attached, ThreadScope attaches it. ART cannot make the thread's Java peer (Exception creating thread peer), and fbjni's result == JNI_OK assertion aborts.
  • From an attached thread, the Java allocation fails and comes back as facebook::jni::JniException, which reaches RuntimeScheduler_Modern::scheduleTask(SchedulerPriority, RawCallback&&) noexcept and ends the process in std::terminate.

While the event loop is scheduled or running, the same post only adds the task to the queue, with no Java call.

Steps to reproduce

The reproducer's C++ TurboModule, NativeFullHeapPost, starts a std::thread that calls a JS callback through AsyncCallback. One second after a button's tap, a second native thread holds the main thread in native code, so that the main thread neither allocates nor meets the full heap first, and fills Java's heap with byte arrays until a 16-byte one fails; half a second later, with the JS thread idle, the first thread calls the callback, which frees the heap.

  1. Clone the reproducer and, with one Android device attached, run ./reproduce.sh. It builds and installs the release APK, runs each button in a launch of its own and reads each launch's logcat.
  2. Or by hand: cd ReproducerApp && yarn install && cd android && ./gradlew assembleRelease && adb install -r app/build/outputs/apk/release/app-release.apk, then open the app and tap one button per launch:
    • Post with the Java heap free: the screen shows that the posted callback ran.
    • Post from a new thread with the Java heap full: the app closes on Abort message: 'result == JNI_OK'.
    • Post from an attached thread with the Java heap full: the app closes on Abort message: 'terminating due to uncaught exception of type facebook::jni::JniException: …'.

Expected: invokeAsync returns to its caller with the heap full, from an attached thread or not, and the callback runs once the JS thread can take it; a post with the heap free keeps running as it does now.
Seen: the process ends inside CallInvokerImpl::invokeAsync, in both cases.

React Native Version

0.90.0-nightly-20261113-6d587e873

Affected Platforms

Runtime - Android

Areas

TurboModule - The New Native Module System

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

System:
  OS: Windows 11 10.0.26300
  CPU: "(32) x64 AMD Ryzen 9 9950X3D 16-Core Processor          "
  Memory: 131.25 GB / 255.67 GB
Binaries:
  Node:
    version: 26.12.1
    path: C:\Program Files\nodejs\node.EXE
  Yarn:
    version: 1.22.22
    path: C:\Users\<user>\AppData\Roaming\npm\yarn.CMD
  npm:
    version: 12.2.1
    path: C:\Program Files\nodejs\npm.CMD
  Watchman: Not Found
SDKs:
  Android SDK: Not Found
  Windows SDK:
    AllowDevelopmentWithoutDevLicense: Enabled
    Versions:
      - 10.0.19041.0
      - 10.0.26100.0
IDEs:
  Android Studio: AI-262.9437.185.2621.16467767
  Visual Studio: Not Found
Languages:
  Java:
    version: 17.0.20.1
    path: C:\Program Files\Microsoft\jdk-17.0.20.101-hotspot\bin\javac.EXE
  Ruby: Not Found
npmPackages:
  "@react-native-community/cli":
    installed: 21.0.0-alpha.1
    wanted: 21.0.0-alpha.1
  react:
    installed: 19.3.0
    wanted: 19.3.0
  react-native:
    installed: 0.90.0-nightly-20261113-6d587e873
    wanted: 0.90.0-nightly-20261113-6d587e873
  react-native-windows: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Stacktrace or Logs

./reproduce.sh at the reproducer's b7f9520, on a Galaxy S23:

MINGW64_NT-10.0-26300 x86_64, node v26.12.1, openjdk version "17.0.20.1" 2026-08-18 LTS
device: SM-S911B, Android 14 (API 34), arm64-v8a, Java heap growth limit 256m
react-native 0.90.0-nightly-20261113-6d587e873
== ./gradlew :app:assembleRelease -PreactNativeArchitectures=arm64-v8a
logs: logs/1-heap-free.log, logs/2-heap-full-new-thread.log, logs/3-heap-full-attached-thread.log
== Post with the Java heap free: invokeAsync returned and the callback ran on the JS thread
   posting the callback from full-heap-post (not attached to the JVM) through CallInvoker::invokeAsync
== Post from a new thread with the Java heap full: SIGABRT in full-heap-post inside CallInvokerImpl::invokeAsync
   Java heap filled: 265177616 of its 268435456 bytes held in byte arrays, a 16-byte array failed
   posting the callback from full-heap-post (not attached to the JVM) through CallInvoker::invokeAsync
   Exception creating thread peer: full-heap-post
   Fatal signal SIGABRT in full-heap-post
   Abort message: 'result == JNI_OK'
   #00 libc.so abort+168
   #01 libart.so art::Runtime::Abort(char const*)+120
   #02 libbase.so android::base::SetAborter(std::__1::function<void (char const*)>&&)::$_0::__invoke(char const*)+80
   #03 liblog.so __android_log_assert+312
   #04 libfbjni.so void facebook::jni::log_::logassert<char const*>(char const*, char const*, char const*)+24
   #05 libfbjni.so
   #06 libfbjni.so facebook::jni::ThreadScope::ThreadScope()+212
   #07 libreactnative.so facebook::react::JMessageQueueThread::runOnQueue(std::__ndk1::function<void ()>&&)+44
   #08 libreactnative.so
   #09 libreactnative.so facebook::react::RuntimeScheduler_Modern::scheduleTask(std::__ndk1::shared_ptr<facebook::react::Task>)+508
   #10 libreactnative.so facebook::react::RuntimeScheduler_Modern::scheduleTask(facebook::react::SchedulerPriority, std::__ndk1::function<void (facebook::jsi::Runtime&)>&&)+208
   #11 libreactnative.so
   #12 libreactnative.so facebook::react::BufferedRuntimeExecutor::execute(facebook::react::SchedulerPriority, std::__ndk1::function<void (facebook::jsi::Runtime&)>&&)+96
   #13 libreactnative.so facebook::react::CallInvokerImpl::invokeAsync(std::__ndk1::function<void (facebook::jsi::Runtime&)>&&)+64
   #14 libappmodules.so facebook::react::AsyncCallback<>::callWithArgs(std::__ndk1::optional<facebook::react::SchedulerPriority>) const+280
== Post from an attached thread with the Java heap full: SIGABRT in full-heap-post inside CallInvokerImpl::invokeAsync
   Java heap filled: 265173520 of its 268435456 bytes held in byte arrays, a 16-byte array failed
   posting the callback from full-heap-post (attached to the JVM before the fill) through CallInvoker::invokeAsync
   Fatal signal SIGABRT in full-heap-post
   Abort message: 'terminating due to uncaught exception of type facebook::jni::JniException: Unable to get exception message.'
   #00 libc.so abort+168
   #01 libc++_shared.so
   #02 libc++_shared.so
   #03 libc++_shared.so
   #04 libc++_shared.so std::terminate()+56
   #05 libreactnative.so
   #06 libreactnative.so facebook::react::RuntimeScheduler_Modern::scheduleTask(facebook::react::SchedulerPriority, std::__ndk1::function<void (facebook::jsi::Runtime&)>&&)+404
   #07 libreactnative.so
   #08 libreactnative.so facebook::react::BufferedRuntimeExecutor::execute(facebook::react::SchedulerPriority, std::__ndk1::function<void (facebook::jsi::Runtime&)>&&)+96
   #09 libreactnative.so facebook::react::CallInvokerImpl::invokeAsync(std::__ndk1::function<void (facebook::jsi::Runtime&)>&&)+64
   #10 libappmodules.so facebook::react::AsyncCallback<>::callWithArgs(std::__ndk1::optional<facebook::react::SchedulerPriority>) const+280
SUMMARY: with the Java heap full, the process ended inside invokeAsync in "Post from a new thread with the Java heap full" and "Post from an attached thread with the Java heap full"; with the heap free the callback ran

MANDATORY Reproducer

https://github.com/YevheniiKotyrlo/rn-invokeasync-full-heap-abort

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