Repository navigation
Description
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,
ThreadScopeattaches it. ART cannot make the thread's Java peer (Exception creating thread peer), and fbjni'sresult == JNI_OKassertion aborts. - From an attached thread, the Java allocation fails and comes back as
facebook::jni::JniException, which reachesRuntimeScheduler_Modern::scheduleTask(SchedulerPriority, RawCallback&&) noexceptand ends the process instd::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.
- 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. - 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
Activity