Crash on sendChangeMessage [Android]

I’m getting a crash which I’ve narrowed down to the sendChangeMessage method. This happens only on Android. The ChangeBroadcaster class that is causing the crash also happens to be instantiated as a SharedResourcePointer. I don’t know if that should make a difference. I might try and make it a Singleton instead.

The crash log is as follows:

                                                            --------- beginning of crash
09-08 20:52:37.371 7253-7297/com.mycompany.myapp A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 7297 (mqt_native_modu)
09-08 20:52:37.392 7253-7263/com.mycompany.myapp W/art: Suspending all threads took: 5.389ms
09-08 20:52:37.476 1202-1202/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
09-08 20:52:37.476 1202-1202/? A/DEBUG: Build fingerprint: 'Android/sdk_google_phone_x86/generic_x86:6.0/MASTER/3038907:userdebug/test-keys'
09-08 20:52:37.476 1202-1202/? A/DEBUG: Revision: '0'
09-08 20:52:37.476 1202-1202/? A/DEBUG: ABI: 'x86'
09-08 20:52:37.477 1202-1202/? A/DEBUG: pid: 7253, tid: 7297, name: mqt_native_modu  >>> com.mycompany.myapp <<<
09-08 20:52:37.477 1202-1202/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
09-08 20:52:37.484 1202-1202/? A/DEBUG:     eax 00000000  ebx a137dad0  ecx 9f8078f0  edx 00000000
09-08 20:52:37.484 1202-1202/? A/DEBUG:     esi 00100646  edi 9eeb9cc8
09-08 20:52:37.484 1202-1202/? A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 00000097  xss 0000007b
09-08 20:52:37.484 1202-1202/? A/DEBUG:     eip a0b7ae5c  ebp 9eeb9c78  esp 9eeb9c60  flags 00210246
09-08 20:52:37.487 1202-1202/? A/DEBUG: backtrace:
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #00 pc 003ace5c  /data/app/com.mycompany.myapp-1/lib/x86/ (_JNIEnv::CallVoidMethodV(_jobject*, _jmethodID*, char*)+10)
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #01 pc 0040775a  /data/app/com.mycompany.myapp-1/lib/x86/ (juce::GlobalRef::callVoidMethod(_jmethodID*, ...) const+64)
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #02 pc 006a24c3  /data/app/com.mycompany.myapp-1/lib/x86/ (juce::MessageManager::postMessageToSystemQueue(juce::MessageManager::MessageBase*)+69)
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #03 pc 0069cf98  /data/app/com.mycompany.myapp-1/lib/x86/ (juce::MessageManager::MessageBase::post()+58)
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #04 pc 0069e1c8  /data/app/com.mycompany.myapp-1/lib/x86/ (juce::AsyncUpdater::triggerAsyncUpdate()+150)
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #05 pc 0069e555  /data/app/com.mycompany.myapp-1/lib/x86/ (juce::ChangeBroadcaster::sendChangeMessage()+55)
09-08 20:52:37.487 1202-1202/? A/DEBUG:     #06 pc 0050f528  /data/app/com.mycompany.myapp-1/lib/x86/ (SharedObjects::resumeApp()+94)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #07 pc 00510eab  /data/app/com.mycompany.myapp-1/lib/x86/ (Java_com_mycompany_myapp_MainActivity_resumeJuceApp+49)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #08 pc 01abab6e  /data/app/com.mycompany.myapp-1/oat/x86/base.odex (offset 0xa90000) (void com.mycompany.myapp.MainActivity.resumeJuceApp()+98)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #09 pc 01934187  /data/app/com.mycompany.myapp-1/oat/x86/base.odex (offset 0xa90000) (void com.mycompany.myapp.ReactScaleController.resume()+187)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #10 pc 00137a82  /system/lib/ (art_quick_invoke_stub+338)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #11 pc 001435c4  /system/lib/ (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+212)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #12 pc 0050f858  /system/lib/ (art::InvokeMethod(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned int)+1736)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #13 pc 0048c5e3  /system/lib/ (art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobject*)+80)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #14 pc 725a8ca4  /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #15 pc 00011eff  [anon:libc_malloc]
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #16 pc 00101e73  [stack:7297]
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #17 pc 00102013  [stack:7297]
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #18 pc 00001fff  [anon:thread signal stack]
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #19 pc 82989e99  <unknown>
09-08 20:52:37.488 1202-1202/? A/DEBUG:     #20 pc 82596436  <unknown>
09-08 20:52:37.696 1521-7454/system_process W/ActivityManager:   Force finishing activity com.mycompany.myapp/com.reactnativenavigation.controllers.NavigationActivity

Can you give us some code to reliably reproduce this? Can you reproduce this with a minimal app that just has a ChangeBroadcaster as a SharedResourcePointer?

I’ll see if I can reproduce it. Thanks

I haven’t had time to create a minimal app to reproduce this yet, but its not to do with SharedResourcePointer necessarily - I changed my class to a Singleton based on std::unique_ptr and it still crashes.

I don’t know if its a red herring or not but it seems to be due to the JNIEnv returning null, from the assertion hit: JUCE Assertion failure in juce_android_SystemStats.cpp:107

JNIEnv* getEnv() noexcept
    JNIEnv* env = androidJNIEnv.get();
    jassert (env != nullptr);    // <--------- assertion failure

    return env;

The message it posts never gets to the Java side, so it looks like the assertion may be causing the crash.

Is there a reason why a Singleton or SharedResourcePointer derived from ChangeBroadcaster might return null for JNIEnv?

I also hit this assertion when calling startThread() on a Thread subclass from the same Singleton.

Hmmm this would happen if the Thread was not created by JUCE. Any chance that the thread was created by another program?

No, definitely created by JUCE! The Thread class is instantiated as a std::unique_ptr member of another class.

Just ran into this error again in a different app. I meant to send an example project to highlight the issue. I will when I get the time (probably after the ADC now!)

My current assumption is that it is to do with threading. Both apps are using React Native, which employs multiple threads.

Actually there is a chance the thread was created by React Native. Would that make more sense?

Yes that would make perfect sense. If you have a thread created by something other than JUCE, then you need to call setEnv on the thread before using any JUCE code. See the comment in juce_android_JNIHelper.h:

// You should rarely need to use this function. Only if you expect callbacks
// on a java thread which you did not create yourself.
extern void setEnv (JNIEnv* env) noexcept;
1 Like

Thanks I’ll give that a go

I’m using JUCE with some other code that creates it’s own thread and would like those threads to be able to post updates to the JUCE GUI. (This is all C++ code, no Java). I’m running into the issue above when trying to run on Android. What do I need to pass to the setEnv() call made by own thread? I tried cacheing the result of getEnv() from the JUCE GUI thread and then using this later as the parameter to setEnv() in the other thread … it makes it pass the Assert() here, but ends up failing anyway. Any ideas would be very helpful

No, every thread has it’s own JNIEnv pointer so you can’t re-use one from another thread.

There are two ways to get a JNIEnv pointer:

  1. In most cases you are currently executing native code because somewhere up the call-stack java code invoked some native method. Check your call-stack if this is the case. The JNI pointer will be supplied to you automatically by java: it is passed as the first argument to the C++ entry point for the native method that java called. You need to somehow store it there (or call just setEnv there if you can).

  2. The native code launched a new thread via pthreads or forking. If this is the case, then you need to create a new JNIEnv pointer via JNI’s AttachCurrentThread. AttachCurrentThread needs a JavaVM pointer which can be shared between different threads. You can get the JavaVM pointer on any thread that already has it’s own JNIEnv via JNI’s GetJavaVM method.

Thanks! My case is #2 and I will try that. Meanwhile I was able to wrap a Juce Thread around my own cross-platform threading code class as a work-around in a pretty clean fashion. I’m currently debugging some deadlock issue that seems to be occurring where my native thread (now a Juce thread) is calling triggerAsyncUpdate() as a cue to the GUI thread component to update some displayed content … it’s only happening on Android (update - solved that issue)