Why my QT app crashes at the destructor of std::thread on Android 10 devices?

I did some research on why my QT app crashes at the destructor of std::thread on Android 10 devices at the user side with the following call stack:

#00 /apex/com.android.runtime/lib64/bionic/libc.so (abort+160)
#01 /system/lib64/libc++.so (abort_message+232)
#02 /system/lib64/libc++.so (demangling_terminate_handler()+44)
#03 /system/lib64/libc++.so (std::__terminate(void (*)())+12)
#04 /system/lib64/libc++.so (std::terminate()+52)
#05 /system/lib64/libc++.so (std::__1::thread::~thread()+20)
#06 /apex/com.android.runtime/lib64/bionic/libc.so (__cxa_finalize+212)
#07 /apex/com.android.runtime/lib64/bionic/libc.so (exit+24)
#08 /data/app/com.domain.myapp-Rs_sm5VrLR1Jj8QW6oYByA==/lib/arm64/libplugins_platforms_qtforandroid_arm64-v8a.so

and figured out that its likely because std::thread destructor is being invoked while the thread is still joinable at some point of the application execution (thanks to G. M. on stackoverflow.com).

I was unable to reproduce this crash in my app on my devices and emulators, but at least I created a small test that demonstrates how std::thread destructor calls std::terminate:

#include "Awl/Testing/TestChain.h"

#include <thread>
#include <chrono>

AWT_TEST(ThreadAbort)
{
    using namespace std::chrono_literals;

    std::thread * p_t = new std::thread([]()
    {
       std::this_thread::sleep_for(5s);
    });

    std::this_thread::sleep_for(1s);

    delete p_t;
}

The result on Android 64 bit is:

#00 /system/lib64/libc.so (abort+104)
#01 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libc++_shared.so
#02 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libc++_shared.so
#03 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libc++_shared.so
#04 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libc++_shared.so (_ZSt9terminatev+52)
#05 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libc++_shared.so (std::__ndk1::thread::~thread()+24)
#06 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libLinesGameQt_arm64-v8a.so
#07 /data/app/com.domain.MyApp-l8euiXwxCtbbVZ_8HvQcMw==/lib/arm64/libLinesGameQt_arm64-v8a.so (Squircle::setUnitTestMode(bool)+128)

and if I compile the test on Win32 with MSVC I get the same result:

std::thread::~thread() Line 57	C++
std::thread::`scalar deleting destructor'(unsigned int)	C++
ThreadAbort_TestFunc(const awl::testing::TestContext & context) Line 20	C++
awl::testing::TestLink::Run(const awl::testing::TestContext & context) Line 30	C++
Squircle::setUnitTestMode(bool val) Line 961	C++
Squircle::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) Line 582	C++
Squircle::qt_metacall(QMetaObject::Call _c, int _id, void * * _a) Line 637	C++

std::thread destructor is implemented like this in MSVC (and likely on all the platforms):

class thread
{
public:
...
    ~thread() noexcept
    {	// clean up
        if (joinable())
            _STD terminate();
    }
...
};

so, obviously, the conclusion is that std::thread object is destroyed at a wrong place on Android 10, but it is not clear where and how to reproduce this in a real-life application.

Less often the app also crashes at the destructor of std::mutex:

#00 /apex/com.android.runtime/lib64/bionic/libc.so (abort+176)
#01 /apex/com.android.runtime/lib64/bionic/libc.so (__fortify_fatal(char const*, ...)+116)
#02 /apex/com.android.runtime/lib64/bionic/libc.so (HandleUsingDestroyedMutex(pthread_mutex_t*, char const*)+52)
#03 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_mutex_destroy+148)
#04 /system/lib64/libc++.so (std::__1::mutex::~mutex()+8)
#05 /apex/com.android.runtime/lib64/bionic/libc.so (__cxa_finalize+212)
#06 /apex/com.android.runtime/lib64/bionic/libc.so (exit+24)
#07 /data/app/com.domain.myapp-hLEaSANDEatnCiV7f2wbeg==/lib/arm64/libplugins_platforms_qtforandroid_arm64-v8a.so

And there are another crashes in __start_thread:

#00 /apex/com.android.runtime/lib64/bionic/libc.so (abort+160)
#01 /apex/com.android.runtime/lib64/bionic/libc.so (__fortify_fatal(char const*, ...)+116)
#02 /apex/com.android.runtime/lib64/bionic/libc.so (HandleUsingDestroyedMutex(pthread_mutex_t*, char const*)+52)
#03 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_mutex_lock+148)
#04 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_cond_wait+72)
#05 /system/lib64/libc++.so (std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&)+20)
#06 /system/lib64/libstagefright_bufferpool@2.0.so (android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl::invalidatorThread(std::__1::map<unsigned int, std::__1::weak_ptr<android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl> const, std::__1::less<unsigned int>, std::__1::allocator<std::__1::pair<unsigned int const, std::__1::weak_ptr<android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl> const>>>&, std::__1::mutex&, std::__1::condition_variable&, bool&) [clone .cfi]+132)
#07 /system/lib64/libstagefright_bufferpool@2.0.so (void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (*)(std::__1::map<unsigned int, std::__1::weak_ptr<android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl> const, std::__1::less<unsigned int>, std::__1::allocator<std::__1::pair<unsigned int const, std::__1::weak_ptr<android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl> const> > >&, std::__1::mutex&, std::__1::condition_variable&, bool&), std::__1::reference_wrapper<std::__1::map<unsigned int, std::__1::weak_ptr<android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl> const, std::__1::less<unsigned int>, std::__1::allocator<std::__1::pair<unsigned int const, std::__1::weak_ptr<android::hardware::media::bufferpool::V2_0::implementation::Accessor::Impl> const> > > >, std::__1::reference_wrapper<std::__1::mutex>, std::__1::reference_wrapper<std::__1::condition_variable>, std::__1::reference_wrapper<bool> > >(void*) (.cfi)+64)
#08 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)
#09 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)
#00 /apex/com.android.runtime/lib64/bionic/libc.so (abort+160)
#01 /apex/com.android.runtime/lib64/bionic/libc.so (__fortify_fatal(char const*, ...)+116)
#02 /apex/com.android.runtime/lib64/bionic/libc.so (HandleUsingDestroyedMutex(pthread_mutex_t*, char const*)+52)
#03 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_mutex_lock+148)
#04 /system/lib64/libc++.so (std::__1::mutex::lock()+8)
#05 /system/lib64/libstagefright_bufferpool@2.0.so (android::hardware::media::bufferpool::V2_0::implementation::ClientManager::getInstance() [clone .cfi]+28)
#06 /system/lib64/libcodec2_hidl_client@1.0.so (android::hardware::media::c2::V1_0::utils::objcpy(std::__1::list<std::__1::unique_ptr<C2Work, std::__1::default_delete<C2Work>>, std::__1::allocator<std::__1::unique_ptr<C2Work, std::__1::default_delete<C2Work>>>>*, android::hardware::media::c2::V1_0::WorkBundle const&) [clone .cfi]+396)
#07 /system/lib64/libcodec2_client.so (android::Codec2Client::Component::HidlListener::onWorkDone(android::hardware::media::c2::V1_0::WorkBundle const&)+60)
#08 /system/lib64/android.hardware.media.c2@1.0.so (android::hardware::media::c2::V1_0::BnHwComponentListener::_hidl_onWorkDone(android::hidl::base::V1_0::BnHwBase*, android::hardware::Parcel const&, android::hardware::Parcel*, std::__1::function<void (android::hardware::Parcel&)>)+196)
#09 /system/lib64/android.hardware.media.c2@1.0.so (android::hardware::media::c2::V1_0::BnHwComponentListener::onTransact(unsigned int, android::hardware::Parcel const&, android::hardware::Parcel*, unsigned int, std::__1::function<void (android::hardware::Parcel&)>)+636)
#10 /system/lib64/libhidlbase.so (android::hardware::BHwBinder::transact(unsigned int, android::hardware::Parcel const&, android::hardware::Parcel*, unsigned int, std::__1::function<void (android::hardware::Parcel&)>)+68)
#11 /system/lib64/libhidlbase.so (android::hardware::IPCThreadState::getAndExecuteCommand()+1036)
#12 /system/lib64/libhidlbase.so (android::hardware::IPCThreadState::joinThreadPool(bool)+96)
#13 /system/lib64/libhidlbase.so (android::hardware::PoolThread::threadLoop()+24)
#14 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+288)
#15 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140)
#16 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)
#17 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)
#00 /apex/com.android.runtime/lib64/bionic/libc.so (abort+160)
#01 /apex/com.android.runtime/lib64/bionic/libc.so (__fortify_fatal(char const*, ...)+116)
#02 /apex/com.android.runtime/lib64/bionic/libc.so (HandleUsingDestroyedMutex(pthread_mutex_t*, char const*)+52)
#03 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_mutex_lock+148)
#04 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_cond_wait+72)
#05 /system/lib64/libc++.so (std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&)+20)
#06 /system/lib64/libhwui.so (android::uirenderer::CommonPool::workerLoop()+96)
#07 /system/lib64/libhwui.so (void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, android::uirenderer::CommonPool::CommonPool()::$_0> >(void*)+132)
#08 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)
#09 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)

they all are on Android 10 and has HandleUsingDestroyedMutex in the call stack.

My QT version is 5.14.2.

Workaround

After some workaround app crash rate reduced a little bit:

but the crashes still occurs.

Links

2 Responses to Why my QT app crashes at the destructor of std::thread on Android 10 devices?

  1. Michael on April 25, 2020 at 1:49 PM:

    Hi this is Michael from Stack Overflow with some additional Info: Both our apps are 95% written in QML but in both apps we added the Helpshift SDK and then the crashes on Android 10 started. So we highly suspect that our implementation (C++ communication with native Java code and the Java Helpshift functions) might be to blame – we’ll look into that now.

    • dmitriano in reply to Michael on April 26, 2020 at 9:43 AM:

      Hi Michael! My QT app extends QtActivity in my custom Java code and uses JNI with QAndroidJniObject and QtAndroid::androidActivity(). The app calls Java from C++ and handles callbacks from Java. Also the app has a lot of QML.

Leave a Reply

Your email address will not be published. Required fields are marked *