QAbstractEventDispatcher: prevent too late unregistering of timers

Timers can't unregister when the dispatcher object is no longer of a
dispatcher type. It's too late even at ~QAbstractEventDispatcher,
because unregisterTimer() is a pure virtual. To prevent their attempting
to unregister, we set the thread's dispatcher to nullptr if it is this
object.

This has been a latent bug, so it's worth fixing. This started happening
for me with an un-pushed change that changed the order of how
QCoreApplication and QGuiApplication destroy the main thread event
dispatcher (namely, in their destructors, not waiting for ~QObject to
deleteChildren()).

Drive-by relax the store in QThread::setEventDispatcher().

Fixes: QTBUG-137130
Pick-to: 6.9 6.8
Change-Id: I8845736c38a931af62e3fffdfd3554874df89e8e
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Thiago Macieira 2025-05-22 17:27:31 -07:00
parent 08325952ee
commit c8d3d7a7af
3 changed files with 27 additions and 2 deletions

View File

@ -175,7 +175,11 @@ QAbstractEventDispatcher::QAbstractEventDispatcher(QAbstractEventDispatcherPriva
Destroys the event dispatcher.
*/
QAbstractEventDispatcher::~QAbstractEventDispatcher()
{ }
{
QThreadData *data = QThreadData::current();
if (data->eventDispatcher.loadRelaxed() == this)
data->eventDispatcher.storeRelaxed(nullptr);
}
/*!
Returns a pointer to the event dispatcher object for the specified

View File

@ -1236,7 +1236,7 @@ void QThread::setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
} else {
eventDispatcher->moveToThread(this);
if (eventDispatcher->thread() == this) // was the move successful?
d->data->eventDispatcher = eventDispatcher;
d->data->eventDispatcher.storeRelaxed(eventDispatcher);
else
qWarning("QThread::setEventDispatcher: Could not move event dispatcher to target thread");
}

View File

@ -83,6 +83,7 @@ private slots:
void singleShotToFunctors();
void singleShot_chrono();
void singleShot_static();
void singleShotDestructionBeforeEventDispatcher();
void crossThreadSingleShotToFunctor_data();
void crossThreadSingleShotToFunctor();
#ifdef QT_BUILD_INTERNAL
@ -1193,6 +1194,22 @@ void tst_QTimer::postedEventsShouldNotStarveTimers()
QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 5, 100);
}
void tst_QTimer::singleShotDestructionBeforeEventDispatcher()
{
// This makes sure the QSingleShotTimer doesn't cause a crash when the
// event dispatcher is deleted. As of the time of this test's writing, the
// QSST was parented to the dispatcher, so if it hadn't yet expired, it
// would be deleted by the QObject destructor, which is too late to
// unregister the timer.
auto thr = QThread::create([] {
QObject o;
QTimer::singleShot(300s, &o, [] {});
});
thr->start();
thr->wait();
}
struct DummyFunctor {
static QThread *callThread;
void operator()() {
@ -1673,6 +1690,10 @@ void tst_QTimer::initMain()
void tst_QTimer::cleanupTestCase()
{
delete s_staticSingleShotUser;
// Same as singleShotDestructionBeforeEventDispatcher() above, but for the
// main thread.
QTimer::singleShot(300s, this, [] {});
}
void tst_QTimer::singleShot_static()