nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Runtime crash due to asynchronous Promise resolution in Android/Kotlin classes

Open dantrongamz opened this issue 7 months ago • 3 comments

What's happening?

Crash happen with the following exception "terminating due to uncaught exception of type std::runtime_error: Unable to retrieve jni environment. Is the thread attached?". The issue is reproducible in my team's app. It is also appears in Nitro example app by letting it running idle (perhaps garbage collection process will run in background).

Reproduceable Code

The test is added in PR reproduce the runtime error. Technically, it happens for any async Promise resolution, followed by GC in JavaScript

    createTest(
      'Async Promise resolution does not cause a garbage-collection crash',
      async () =>
        (
          // This test demonstrates a crashing bug seen in Nitro modules 0.25.2, only on
          // Android (Kotlin). Run this test individually (not in "Run All Tests") after a
          // fresh launch of NitroExample app for best repeatability of the crash. If
          // repeated runs of this test do not reproduce a crash, you may need to increase
          // memory use (or decrease available memory) to encourage garbage collection.

          // The crash logs this message:
          // "terminating due to uncaught exception of type std::runtime_error: Unable to retrieve jni environment. Is the thread attached?"
          // from the "hades" thread, which is used for garbage collection in the Hermes JS
          // engine. The crash results from execution of the C++ Promise destructor on a thread
          // that's not attached to the JVM. The origin is noted in the native backtrace as:
          // "(margelo::nitro::Promise<double>::~Promise()+96)"

          await it(async () => {
            return timeoutedPromise(async (complete) => {
              // This section creates a Nitro Promise that resolves asynchronously. The crash
              // seen on Android/Kotlin in Nitro modules 0.25.2 requires that this is resolved
              // after a delay, and that garbage collection happens after it is resolved.
              const asyncPromise = testObject.callbackAsyncPromise(() => new Promise((resolve) =>
                setTimeout(() => {
                  resolve(13)
                }, 1_000)
              ));

              // This section only exists to exercise the JS garbage collector, by creating
              // large strings that temporarily consume enough memory to trigger GC. Values
              // can be adjusted to increase memory use.
              const garbage: Array<string> = []
              let countdown = 200
              let resolveGarbagePromise = () => {}
              const garbagePromise = new Promise<void>(resolve => {
                resolveGarbagePromise = resolve
              })
              let interval = setInterval(() => {
                if (countdown-- > 0) {
                  if (countdown % 10 == 0) {
                    garbage.length = 0
                  } else {
                    garbage.push("x".repeat(2_000_000))
                  }
                } else {
                  clearInterval(interval)
                  resolveGarbagePromise()
                }
              }, 50)

              // This section waits on the async things to complete before passing the test.
              // As of Nitro modules 0.25.2, this test should succeed for Swift and C++, and
              // and it should frequently crash the Android app for Kotlin.
              const result = await asyncPromise
              await garbagePromise
              complete(result)
            }, 20_000)
          })
        )
          .didNotThrow()
          .equals(13)

Relevant log output

2025-05-09 12:12:53.770 10740-10766 libc++abi               com.margelo.nitroexample             E  terminating due to uncaught exception of type std::runtime_error: Unable to retrieve jni environment. Is the thread attached?
2025-05-09 12:12:53.770 10740-10766 libc                    com.margelo.nitroexample             A  Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 10766 (hades), pid 10740 (lo.nitroexample)
2025-05-09 12:12:53.806 10794-10794 crash_dump64            pid-10794                            I  obtaining output fd from tombstoned, type: kDebuggerdTombstoneProto
2025-05-09 12:12:53.808   197-197   tombstoned              tombstoned                           I  received crash request for pid 10766
2025-05-09 12:12:53.808 10794-10794 crash_dump64            pid-10794                            I  performing dump of process 10740 (target tid = 10766)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  Build fingerprint: 'google/sdk_gphone64_arm64/emu64a:13/TE1A.240213.009/12342917:user/release-keys'
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  Revision: '0'
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  ABI: 'arm64'
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  Timestamp: 2025-05-09 12:12:53.812017936-0700
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  Process uptime: 16s
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  Cmdline: com.margelo.nitroexample
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  pid: 10740, tid: 10766, name: hades  >>> com.margelo.nitroexample <<<
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  uid: 10175
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  Abort message: 'terminating due to uncaught exception of type std::runtime_error: Unable to retrieve jni environment. Is the thread attached?'
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x0  0000000000000000  x1  0000000000002a0e  x2  0000000000000006  x3  000000726b40e620
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x4  646277641f736766  x5  646277641f736766  x6  646277641f736766  x7  7f7f7f7f7f7f7f7f
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x8  00000000000000f0  x9  00000075a1baea00  x10 0000000000000001  x11 00000075a1becde4
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x12 00000075ae737020  x13 000000007fffffff  x14 0000000000333b90  x15 000011b106f9f6df
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x16 00000075a1c51d58  x17 00000075a1c2ec70  x18 000000726aae8000  x19 00000000000029f4
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x20 0000000000002a0e  x21 00000000ffffffff  x22 000000726b40e750  x23 000000726b40e790
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x24 000000726b40e840  x25 00000004f83f88c8  x26 0000000000000030  x27 0000000000000638
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      x28 00000004f8400000  x29 000000726b40e6a0
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A      lr  00000075a1bde968  sp  000000726b40e600  pc  00000075a1bde994  pst 0000000000001000
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A  backtrace:
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #00 pc 0000000000051994  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: 4e07915368c859b1910c68c84a8de75f)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #01 pc 00000000000a0210  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libc++_shared.so (offset 0x70c000) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #02 pc 000000000009eeb0  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libc++_shared.so (offset 0x70c000) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #03 pc 000000000009f374  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libc++_shared.so (offset 0x70c000) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #04 pc 000000000009f314  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libc++_shared.so (offset 0x70c000) (std::terminate()+56) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #05 pc 000000000025c2a8  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libNitroImage.so (offset 0x1b8000) (BuildId: d081aff7f0dbf08942e19340f69dba7533485e6d)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #06 pc 0000000000283968  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libNitroImage.so (offset 0x1b8000) (BuildId: d081aff7f0dbf08942e19340f69dba7533485e6d)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #07 pc 0000000000313ce8  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libNitroImage.so (offset 0x1b8000) (margelo::nitro::Promise<margelo::nitro::image::Person>::~Promise()+96) (BuildId: d081aff7f0dbf08942e19340f69dba7533485e6d)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #08 pc 0000000000313bb4  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libNitroImage.so (offset 0x1b8000) (BuildId: d081aff7f0dbf08942e19340f69dba7533485e6d)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #09 pc 0000000000314fec  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libNitroImage.so (offset 0x1b8000) (BuildId: d081aff7f0dbf08942e19340f69dba7533485e6d)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #10 pc 00000000003149dc  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libNitroImage.so (offset 0x1b8000) (BuildId: d081aff7f0dbf08942e19340f69dba7533485e6d)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #11 pc 00000000000ba748  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libhermes.so (offset 0x874000) (BuildId: a6f7855bb11897bd23c29123dc102e3f0f9b55b7)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #12 pc 0000000000168a70  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libhermes.so (offset 0x874000) (BuildId: a6f7855bb11897bd23c29123dc102e3f0f9b55b7)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #13 pc 000000000016a45c  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libhermes.so (offset 0x874000) (BuildId: a6f7855bb11897bd23c29123dc102e3f0f9b55b7)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #14 pc 000000000016f3f8  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libhermes.so (offset 0x874000) (BuildId: a6f7855bb11897bd23c29123dc102e3f0f9b55b7)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #15 pc 000000000016e518  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libhermes.so (offset 0x874000) (BuildId: a6f7855bb11897bd23c29123dc102e3f0f9b55b7)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #16 pc 000000000016e3dc  /data/app/~~Ii46yVgEXLzQEpLHNMKpEQ==/com.margelo.nitroexample-6r1jzf7RijwR2suKifbQbA==/base.apk!libhermes.so (offset 0x874000) (BuildId: a6f7855bb11897bd23c29123dc102e3f0f9b55b7)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #17 pc 00000000000b63b0  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208) (BuildId: 4e07915368c859b1910c68c84a8de75f)
2025-05-09 12:12:53.970 10794-10794 DEBUG                   pid-10794                            A        #18 pc 00000000000530b8  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 4e07915368c859b1910c68c84a8de75f)
2025-05-09 12:12:53.980   197-197   tombstoned              tombstoned                           E  Tombstone written to: tombstone_03

From my team's investigation using the above base addresses and the virtual addresses from the call stack and the name of the thread we can see that:

  • Based on the thread name 'hades' and looking up the Hermes code base, that thread is a GC thread
  • frame 10 confirms that Hermes was finalizing host function context: facebook::hermes::HermesRuntimeImpl::HFContext::finalize
  • frame 9 confirm that it was park of the GC cylce: hermes::vm::HadesGC::OldGen::sweepNext Looking at frame 5 we can tell that Nitros promise object that is JSI bound is being destroyed

From the above observations, what happens is that Herme's GC finalizes JSI bound Nitro promise for return output which is still pending (not resolved or rejected). That promise also holds resources to invoke Kotlin/Java's callback (reject/resolve callbacks).

When those resources are attempted to be freed as part of the promise destruction it crashes the app because those are JNI bound resources attempted to be freed on non JNI attached thread ('hades' thread is not attached to the JNI).

Device

Android 14 device and emulator

Nitro Modules Version

0.25.1

Nitrogen Version

0.25.1

Can you reproduce this issue in the Nitro Example app here?

Yes, I can reproduce the same issue in the Example app here: https://github.com/mrousavy/nitro/pull/664

Additional information

dantrongamz avatar May 21 '25 20:05 dantrongamz

Hey thanks for the detailed bug report! Can you try to patch this fix into Nitro Core to see if that fixes your bug? https://github.com/mrousavy/nitro/commit/0589b256332c7d08370546f1652a0cd1cc467673

mrousavy avatar May 23 '25 14:05 mrousavy

Thanks @mrousavy . I have tried with the latest code from main branch in a new PR https://github.com/mrousavy/nitro/pull/673 When click on running the test in example app "Async Promise resolution does does not cause a garbage-collection crash", it still crash the app. Here is the full stack trace

2025-05-23 12:39:48.005 16595-16648 libc++abi               com.margelo.nitroexample             E  terminating due to uncaught exception of type std::runtime_error: Unable to retrieve jni environment. Is the thread attached?
2025-05-23 12:39:48.005 16595-16648 libc                    com.margelo.nitroexample             A  Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 16648 (hades), pid 16595 (lo.nitroexample)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  Build fingerprint: 'google/sdk_gphone64_arm64/emu64a:14/UE1A.230829.036.A4/12096271:user/release-keys'
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  Revision: '0'
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  ABI: 'arm64'
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  Timestamp: 2025-05-23 12:39:48.063167455-0400
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  Process uptime: 112s
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  Cmdline: com.margelo.nitroexample
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  pid: 16595, tid: 16648, name: hades  >>> com.margelo.nitroexample <<<
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  uid: 10191
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  Abort message: 'terminating due to uncaught exception of type std::runtime_error: Unable to retrieve jni environment. Is the thread attached?'
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x0  0000000000000000  x1  0000000000004108  x2  0000000000000006  x3  000000784a2ff640
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x4  646277641f736766  x5  646277641f736766  x6  646277641f736766  x7  7f7f7f7f7f7f7f7f
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x8  00000000000000f0  x9  0000007afacb4090  x10 0000000000000001  x11 0000007afad07058
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x12 000000006830a4d4  x13 000000007fffffff  x14 00000000001b0822  x15 0000005770d5e0d9
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x16 0000007afad74d08  x17 0000007afad48e90  x18 0000007849324000  x19 00000000000040d3
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x20 0000000000004108  x21 00000000ffffffff  x22 000000784a2ff770  x23 000000784a2ff7b0
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x24 000000784a2ff860  x25 0000000000000006  x26 00000035fcc00000  x27 b400007b2c161190
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      x28 0000000000000130  x29 000000784a2ff6c0
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A      lr  0000007afacf89b8  sp  000000784a2ff620  pc  0000007afacf89e4  pst 0000000000001000
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  19 total frames
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A  backtrace:
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #00 pc 00000000000669e4  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: a87908b48b368e6282bcc9f34bcfc28c)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #01 pc 00000000000a0210  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libc++_shared.so (offset 0xd24000) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #02 pc 000000000009eeb0  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libc++_shared.so (offset 0xd24000) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #03 pc 000000000009f374  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libc++_shared.so (offset 0xd24000) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #04 pc 000000000009f314  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libc++_shared.so (offset 0xd24000) (std::terminate()+56) (BuildId: 6783a7f3a0d9c67c55a74cadc441ed55aaa493da)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #05 pc 0000000000274158  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #06 pc 000000000029622c  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (facebook::jni::base_owned_ref<facebook::jni::detail::JTypeFor<facebook::jni::HybridClass<margelo::nitro::JPromise, facebook::jni::detail::BaseHybridClass>::JavaPart, facebook::jni::JObject, void>::_javaobject*, facebook::jni::GlobalReferenceAllocator>::reset(facebook::jni::detail::JTypeFor<facebook::jni::HybridClass<margelo::nitro::JPromise, facebook::jni::detail::BaseHybridClass>::JavaPart, facebook::jni::JObject, void>::_javaobject*)+248) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #07 pc 0000000000297818  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #08 pc 0000000000288b84  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (margelo::nitro::Promise<double>::~Promise()+96) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #09 pc 0000000000288a50  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #10 pc 000000000030b190  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #11 pc 00000000002dae1c  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libNitroImage.so (offset 0x5ec000) (BuildId: 33fab5f1f5c457e2716b7192e261a52423f7dbb8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #12 pc 00000000000eae58  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libhermes.so (offset 0xe8c000) (BuildId: 84c243bb28f42eda0d52512da73b6dc33714c1f8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #13 pc 00000000002450dc  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libhermes.so (offset 0xe8c000) (BuildId: 84c243bb28f42eda0d52512da73b6dc33714c1f8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #14 pc 000000000024dad4  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libhermes.so (offset 0xe8c000) (BuildId: 84c243bb28f42eda0d52512da73b6dc33714c1f8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #15 pc 000000000024cce0  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libhermes.so (offset 0xe8c000) (BuildId: 84c243bb28f42eda0d52512da73b6dc33714c1f8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #16 pc 000000000024cab0  /data/app/~~95rW28Bv2IyfHwDJxLJStw==/com.margelo.nitroexample-Ak-ivVjCfEsBmygusAAIIQ==/base.apk!libhermes.so (offset 0xe8c000) (BuildId: 84c243bb28f42eda0d52512da73b6dc33714c1f8)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #17 pc 00000000000cb6a8  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208) (BuildId: a87908b48b368e6282bcc9f34bcfc28c)
2025-05-23 12:39:48.250 16764-16764 DEBUG                   pid-16764                            A        #18 pc 000000000006821c  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: a87908b48b368e6282bcc9f34bcfc28c)
2025-05-23 12:39:48.259   206-206   tombstoned              tombstoned                           E  Tombstone written to: tombstone_01

dantrongamz avatar May 23 '25 16:05 dantrongamz

Good day, I have picked interest in this git issues and I would like to solve it since I have experience with kotlin and react native

EmmanuelIbiyemi avatar May 29 '25 02:05 EmmanuelIbiyemi

FYI, I attempted to update the de-constructor logic of JPromise.hpp in https://github.com/mrousavy/nitro/commit/84580e6630e0a9c13f3a780ce34ea0fd6016a7f3 but it doesn't fix this issue yet.

dantrongamz avatar Jun 02 '25 18:06 dantrongamz

@dantrongamz your fix in ~JPromise looks like it should solve the issue - are you sure it still crashes with the same?

mrousavy avatar Jun 05 '25 10:06 mrousavy

Hmm it's crashing here

Image

mrousavy avatar Jun 05 '25 11:06 mrousavy

Okay 40 minutes later I found the bug.

Image

The problem is that the ~Promise() destructor: https://github.com/mrousavy/nitro/blob/2ae634c81f570a098ae0a393d970e448a339d0d2/packages/react-native-nitro-modules/cpp/core/Promise.hpp#L36-L41

Is obviously destroying it's fields: https://github.com/mrousavy/nitro/blob/2ae634c81f570a098ae0a393d970e448a339d0d2/packages/react-native-nitro-modules/cpp/core/Promise.hpp#L248-L252

And one of those fields contains a JNI value. To be more explicit, it's one of the OnResolvedFunc inside the _onResolvedListeners; https://github.com/mrousavy/nitro/blob/2ae634c81f570a098ae0a393d970e448a339d0d2/packages/react-native-nitro-modules/cpp/core/Promise.hpp#L251

Because if we take a look at the usage of Promise in JFunc_std__shared_ptr_Promise_double__ , we can see that the function passed to addOnResolvedListener(...) captures __promise - a JNI value: https://github.com/mrousavy/nitro/blob/2ae634c81f570a098ae0a393d970e448a339d0d2/packages/react-native-nitro-image/nitrogen/generated/android/c%2B%2B/JFunc_std__shared_ptr_Promise_double__.hpp#L65-L77

... So this call here: https://github.com/mrousavy/nitro/blob/2ae634c81f570a098ae0a393d970e448a339d0d2/packages/react-native-nitro-image/nitrogen/generated/android/c%2B%2B/JFunc_std__shared_ptr_Promise_double__.hpp#L69-L71

Captures __promise by value, and after it already completed, the function here is the last strong reference to __promise. Once the holder Promise gets destroyed, so does this function, and so do all of it's captured members - aka __promise.

Since this is happening from JS' GC on a Hades Thread, it's not really possible to keep this deterministic. By design, obviously. I'll think of something.

mrousavy avatar Jun 05 '25 11:06 mrousavy

PR is up: https://github.com/mrousavy/nitro/pull/683

mrousavy avatar Jun 05 '25 12:06 mrousavy

Thanks a bunch for this tricky fix! Our team has been eagerly waiting!

dantrongamz avatar Jun 05 '25 15:06 dantrongamz

Thanks a bunch for this tricky fix!

Sure thing! Was a fun one.

Our team has been waiting for it for ages.

Next time just contact us through my agency or do a GitHub sponsorship - I only do open-source if it's in my own interest too. If someone has a bug or feature I don't experience myself, I usually don't work on it unless I'm paid to.

mrousavy avatar Jun 05 '25 18:06 mrousavy

I'm seeing a similar error with the latest version, but I can't seem to repro by using the example. The stacktrace references this callback EventHandle:

export interface EventHandle {
  remove(): void;
}
....
subscribe(listener: () => void): EventHandle;

I tried to reproduce it on this branch to no avail https://github.com/mrousavy/nitro/compare/main...joprice:nitro:subscribe#diff-f638d88bea117f8133046adda5f40a5ffc7edb6939e83f436749df48b62b1e1dR318

~~Odd thing is, in my own repo where it's failing, I commented out all usage of the EventHandle type and it still fails after a few reloads of react refresh.~~

I'm able to consistently reproduce this (but still only in my project) with an effect that has a teardown:

  useEffect(() => {
    const sub = Shake.addListener(() => {
      ...
    });
    return () => {
      sub.remove();
    };
  }, []);

If I remove the cleanup function, it doesn't crash.

Here's the stack: Looks like it's deallocating the global ref and the jni is not available. I tried sprinkling in some jni::ThreadScope ts; as I saw in some other issues, but the jni doesn't seem to ever be available. Perhaps a custom function needs to be used there so the jni can be grabbed in the destructor in stead of an anonymous lambda?

abort 0x000000732b756134
[Inlined] facebook::jni::GlobalReferenceAllocator::verifyReference(_jobject *) const ReferenceAllocators-inl.h:104
[Inlined] facebook::jni::GlobalReferenceAllocator::deleteReference(_jobject *) const ReferenceAllocators-inl.h:97
facebook::jni::base_owned_ref::reset(facebook::jni::detail::JTypeFor<…>::_javaobject *) References-inl.h:293
[Inlined] facebook::jni::base_owned_ref::reset() References-inl.h:286
[Inlined] facebook::jni::base_owned_ref::~base_owned_ref() References-inl.h:272
[Inlined] margelo::nitro::audioplayer::JEventHandle::toCpp() const::'lambda'()::operator()() const::'lambda'()::~() JEventHandle.hpp:49
[Inlined] margelo::nitro::JSIConverter<std::__ndk1::function<void ()>, void>::toJSI(facebook::jsi::Runtime&, std::__ndk1::function<void ()>&&)::'lambda'(facebook::jsi::Runtime&, facebook::jsi::Value const&, facebook::jsi::Value const*, unsigned long)::~() JSIConverter+Function.hpp:51
[Inlined] std::__ndk1::function::~function() Function.h:969
[Inlined] std::__ndk1::__invoke[abi:nn180000]<…>($_0 &) Invoke.h:344
[Inlined] std::__ndk1::__invoke_void_return_wrapper::__call[abi:nn180000]<…>($_0 &) Invoke.h:419
[Inlined] std::__ndk1::__function::__alloc_func::operator()[abi:nn180000]() Function.h:166
std::__ndk1::__function::__func::operator()() Function.h:308
[Inlined] std::__ndk1::__function::__value_func::operator()[abi:nn180000]() const Function.h:425
[Inlined] std::__ndk1::function::operator()() const Function.h:978
[Inlined] decltype(std::declval<hermes::vm::HadesGC::Executor::Executor()::'lambda'()>()()) std::__ndk1::__invoke[abi:nn180000]<hermes::vm::HadesGC::Executor::Executor()::'lambda'()>(hermes::vm::HadesGC::Executor::Executor()::'lambda'()&&) Invoke.h:344
[Inlined] void std::__ndk1::__thread_execute[abi:nn180000]<std::__ndk1::unique_ptr<std::__ndk1::__thread_struct, std::__ndk1::default_delete<std::__ndk1::__thread_struct>>, hermes::vm::HadesGC::Executor::Executor()::'lambda'()>(std::__ndk1::tuple<std::__ndk1::unique_ptr<std::__ndk1::__thread_struct, std::__ndk1::default_delete<std::__ndk1::__thread_struct>>, hermes::vm::HadesGC::Executor::Executor()::'lambda'()>&, std::__ndk1::__tuple_indices<...>) thread.h:190
std::__ndk1::__thread_proxy[abi:nn180000]<…>(void *) thread.h:199

joprice avatar Nov 19 '25 05:11 joprice

I got it to stop crashing by wrapping it in a type that uses ThreadScope in the destructor:

  template<typename T>
    struct JniSafeUnsubscriber {
        facebook::jni::global_ref<T> ref;

        explicit JniSafeUnsubscriber(facebook::jni::global_ref<T> r)
                : ref(std::move(r)) {}

        ~JniSafeUnsubscriber() {
            if (ref) {
                facebook::jni::ThreadScope ts;
                ref.reset();
            }
        }

        T *operator->() {
            return ref.get();
        }

        T &operator*() {
            return *ref;
        }

        explicit operator bool() const {
            return static_cast<bool>(ref);
        }

        JniSafeUnsubscriber(const JniSafeUnsubscriber &) = delete;

        JniSafeUnsubscriber &operator=(const JniSafeUnsubscriber &) = delete;

        JniSafeUnsubscriber(JniSafeUnsubscriber &&) = default;

        JniSafeUnsubscriber &operator=(JniSafeUnsubscriber &&) = default;

    };

....

 auto unsubscribeRef = jni::make_global(unsubscribe);
  auto safeWrapper = std::make_shared<JniSafeUnsubscriber<JFunc_void::javaobject>>(
          std::move(unsubscribeRef));
  return [safeWrapper = std::move(safeWrapper)]() mutable -> void {
      auto &originalRef = safeWrapper->ref;
      return originalRef->invoke();
  };

joprice avatar Nov 19 '25 17:11 joprice

where would that code be? is there anything that Nitro currently does wrong? is there a place where Nitro should add a ThreadScope?

mrousavy avatar Nov 19 '25 21:11 mrousavy

Yea this is in the generated cpp code for an interface with a callback defined in a nitro file so nitro would want to update its codegen to make sure any closure that captures a jni value is wapped in an wrapper that handles jni scope. I think other types like promise do this already. It’s just cpp closures which have an automatically generated destructor that will call the destructor of captured arguments. This is assuming ownership moves into that function. I’m not familiar with the jni library for cpp to know whether there’s some other approach with global refs like capturing by a weak ref and letting them be cleaned up automatically but I think there was a pr that explicitly introduced the global ref for similar reasons.

joprice avatar Nov 19 '25 23:11 joprice

Ahh yea it's because the closure holds something that can be destroyed from an arbitrary thread (Hades GC)

How often did you actually see this error and can you reproduce it in your app in debug?

mrousavy avatar Nov 19 '25 23:11 mrousavy

Ever 5th save or so. It was driving me crazy. I almost started rewriting the android target in kotlin haha but finally bit the bullet and spent two days getting to the bottom of it. I can reproduce it easily but I can’t seem to recreate it in the demo app in the nitro repo.

joprice avatar Nov 20 '25 00:11 joprice

Fixed in https://github.com/mrousavy/nitro/pull/1052

mrousavy avatar Nov 20 '25 14:11 mrousavy