Details
-
Bug
-
Resolution: Fixed
-
P2: Important
-
5.15.2
-
None
-
qt-5.15
emscripten 2.0.8
ubuntu 18.04
Description
Recently We found some deadlocks in our web-assembly app, after some investigation I found that the deadlock is caused by reentrant of Qt event handling with multi-threaded qt-wasm 5.15. I build qt-5.15 from git with emscripten 2.0.8 with thread enabled.
Below is a callstack when deadlock happened:
_emscripten_futex_wait (VM12:8491)
__timedwait_cp (type_traits:2261)
__timedwait (type_traits:2261)
__pthread_mutex_timedlock (type_traits:2261)
__pthread_mutex_lock (type_traits:2261)
std::_2::_libcpp_mutex_lock(pthread_mutex_t*) (type_traits:2261)
std::__2::mutex::lock() (type_traits:2261)
std::_2::lock_guard<std::2::mutex>::lock_guard(std::2::mutex&) (_mutex_base:91)
CNvPlatformEventHandler::SendPostedEventByEventHandlerId(long long) (NvPlatformEventHandler.cpp:246)
CNvQtEventHandler::customEvent(QEvent*) (NvPlatformEventHandler.cpp:115)
QObject::event(QEvent*) (type_traits:2261)
QCoreApplicationPrivate::notify_helper(QObject*, QEvent*) (type_traits:2261)
QCoreApplication::notify(QObject*, QEvent*) (type_traits:2261)
QGuiApplication::notify(QObject*, QEvent*) (type_traits:2261)
QCoreApplication::notifyInternal2(QObject*, QEvent*) (type_traits:2261)
QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (type_traits:2261)
QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (type_traits:2261)
QUnixEventDispatcherQPA::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (type_traits:2261)
QWasmEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (type_traits:2261)
QWasmEventDispatcher::mainThreadWakeUp(void*) (type_traits:2261)
_do_call (type_traits:2261)
emscripten_current_thread_process_queued_calls (type_traits:2261)
emscripten_main_thread_process_queued_calls (type_traits:2261)
__timedwait_cp (type_traits:2261)
__timedwait (type_traits:2261)
__pthread_mutex_timedlock (type_traits:2261)
__pthread_mutex_lock (type_traits:2261)
dlfree (type_traits:2261)
SNvArrayData::deallocate(SNvArrayData*, unsigned long, unsigned long) (NvArrayData.cpp:131)
SNvTypedArrayData<unsigned short>::deallocate(SNvArrayData*) (NvArrayData.h:134)
CNvString::~CNvString() (NvString.h:786)
CNvDebugLog::Stream::~Stream() (NvDebug.h:101)
CNvDebugLog::DropStream() (NvDebug.cpp:372)
CNvDebugLog::~CNvDebugLog() (NvDebug.cpp:128)
CNvWebReaderManager::TryAbortWebRequest() (NvWebReaderManager.cpp:658)
CNvWebReaderManager::SendWebRequest(CNvBaseWebReader*, int, int, bool) (NvWebReaderManager.cpp:609)
CNvWebReaderManager::ProcessEvent(CNvEvent*) (NvWebReaderManager.cpp:422)
CNvPlatformEventHandler::SendPostedEvent(int) (NvPlatformEventHandler.cpp:217)
CNvPlatformEventHandler::SendPostedEventByEventHandlerId(long long) (NvPlatformEventHandler.cpp:249)
CNvQtEventHandler::customEvent(QEvent*) (NvPlatformEventHandler.cpp:115)
QObject::event(QEvent*) (type_traits:2261)
QCoreApplicationPrivate::notify_helper(QObject*, QEvent*) (type_traits:2261)
QCoreApplication::notify(QObject*, QEvent*) (type_traits:2261)
QGuiApplication::notify(QObject*, QEvent*) (type_traits:2261)
QCoreApplication::notifyInternal2(QObject*, QEvent*) (type_traits:2261)
QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (type_traits:2261)
QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (type_traits:2261)
QUnixEventDispatcherQPA::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (type_traits:2261)
QWasmEventDispatcher::doMaintainTimers()::$1::_invoke(void*) (type_traits:2261)
The scenario is that we post QEvent from a child thread to the main thread to trigger main thread working on a work queue, in the main thread's qt event handler we will do work in the work queue one by one with a lock held. To my surprise QWasmEventDispatcher::processEvents() get called while we do work of the queue as you can see in the callstack:
QWasmEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (type_traits:2261)
QWasmEventDispatcher::mainThreadWakeUp(void*) (type_traits:2261)
_do_call (type_traits:2261)
emscripten_current_thread_process_queued_calls (type_traits:2261)
emscripten_main_thread_process_queued_calls (type_traits:2261)
__timedwait_cp (type_traits:2261)
__timedwait (type_traits:2261)
__pthread_mutex_timedlock (type_traits:2261)
__pthread_mutex_lock (type_traits:2261)
dlfree (type_traits:2261)
That is to say: when I call ::free() C runtime function implemented by emscritpen, emscritpen will call emscripten_main_thread_process_queued_calls() in the mean time. (I think this is deliberately since emscripten always proxy some work to main thread which is called from child thread) But that trigger a QWasmEventDispatcher::mainThreadWakeUp() function call and then QWasmEventDispatcher::processEvents() get called. A reentrant happened!
I know I can solve my deadlock with a very simple solution, But I think the more serious problem is Qt event handling reentrant which will cause unexpected problems.