Details
-
Bug
-
Resolution: Fixed
-
P1: Critical
-
6.7, 6.8
-
None
-
-
2bc82f6a1 (dev), 1873ff034 (6.8), f1b230b88 (6.7)
-
Foundation PM Prioritized
Description
TL;DR
There is a race condition in function:
[..\qtbase\src\corelib\thread\qfutureinterface.cpp]
void QtPrivate::watchContinuationImpl(const QObject *context, QSlotObjectBase *slotObj, QFutureInterfaceBase &fi)
Context
Since moving to Qt 6.7 our users started encountering crash with the following stack trace:
Fatal Error: EXCEPTION_ACCESS_VIOLATION_READ / 0x20 Qt6Core std::_Atomic_storage<T>::load (atomic:1134) Qt6Core QObjectPrivate::ConnectionData::cleanOrphanedConnections (qobject_p_p.h:172) Qt6Core doActivate<T> (qobject.cpp:4081) Qt6Core QMetaObject::activate (qobject.cpp:4099) Qt6Core QObjectContinuationWrapper::run (qfutureinterface.moc:159) Qt6Core QtPrivate::watchContinuationImpl::__l2::<lambda>::operator() (qfutureinterface.cpp:101) Qt6Core std::invoke (type_traits:1534) Qt6Core std::_Invoker_ret<T>::_Call (functional:651) Qt6Core std::_Func_impl_no_alloc<T>::_Do_call (functional:822) Qt6Core std::_Func_class<T>::operator() (functional:869) Qt6Core QFutureInterfaceBase::runContinuation (qfutureinterface.cpp:932) Qt6Core QThreadPoolThread::run (qthreadpool.cpp:66) Qt6Core QThreadPrivate::start (qthread_win.cpp:290)
This is by far the most frequently occurring crash in our application.
Description
Crash happens when Asynchronous task continuation is scheduled on the context thread.
#include <QCoreApplication> #include <QtConcurrent> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); auto context = new QObject; QtConcurrent::run([] { qDebug() << "Task on the pooled thread" << QThread::currentThreadId(); }).then(context, [] { qDebug() << "Continuation on the context (main) thread" << QThread::currentThreadId(); }); return a.exec(); }
Crash will happen rarely in production, but can be reliably reproduced by introducing artificial delays in Qt source code. Example output with artificial delay*:
[doActivate<T>] Delay on thread 0x298c Continuation on the context (main) thread 0x23e8 [~QObjectContinuationWrapper<T>] Deleting watcher on thread 0x23e8 - Stopped in thread 4 by: Exception at 0x7ffc29b285ab, code: 0xc0000005: read access violation at: 0xffffffffffffffff, flags=0x0.
Analysis
[..\qtbase\src\corelib\thread\qfutureinterface.cpp] (non-essential parts omitted for brevity, comments added) void QtPrivate::watchContinuationImpl(const QObject *context, QSlotObjectBase *slotObj, QFutureInterfaceBase &fi) { ... auto *watcher = new QObjectContinuationWrapper; // POI 1 This is on the main thread watcher->moveToThread(context->thread()); ... QObject::connect(watcher, &QObjectContinuationWrapper::run, context, [slot = std::move(slot)] { void *args[] = { nullptr }; slot->call(nullptr, args); }); // POI 3 QObject::connect(watcher, &QObjectContinuationWrapper::run, watcher, &QObject::deleteLater); // POI 4 ... fi.setContinuation([watcherMutex, watcher = QPointer(watcher)] (const QFutureInterfaceBase &parentData) { Q_UNUSED(parentData); QMutexLocker lock(watcherMutex.get()); if (watcher) emit watcher->run(); // POI 2 This is from the pooled thread }); }
Future signals QObjectContinuationWrapper (POI 1 ) to run from the different thread. Because connected slot (POI 3 and 4) contexts are on the main thread, slots are asynchronously dispatched to the main thread (refer to doActivate<T> (qobject.cpp: 4042)). In rear occasions, deleteLater (POI 4) will be triggered first and {}watcher cleaned up in the event loop cycle before doActivate<T> (qobject.cpp from the pooled thread, POI 2){{ is finished.}}
*Attached are QObject and QFutureIntefrace implementation files with minor modifications (artificial delay and debug output) to showcase the issue. Sources were taken from Qt 6.7.1
Attachments
Issue Links
- duplicates
-
QTBUG-126013 heap-use-after-free in from QtPrivate::watchContinuationImpl
- Closed
- relates to
-
QTBUG-118747 Sporadic crashes when calling deleteLater() on the sender of a signal
- Reported