QThread/Unix: make QThreadPrivate::finish() be called much later
We need it to run after all the thread-local destructors have run, to ensure that some user code hasn't run after QThreadPrivate::finish() has finished. We achieve that by making it get called from a thread-local destructor itself, in the form of a qScopeGuard. This ought to have been done since C++11 thread_local with non-trivial destructors became available. However, it only started showing up after commit 4a93285b166ceceaea2e10c8fc6a254d2f7093b9 began using thread_local inside Qt itself. The visible symptom was that QThreadPrivate::finish() had already destroyed the thread's event dispatcher, but some user code ran later and expected it to still exist (or, worse, recreated it, via QEventLoop → QThreadData::ensureEventDispatcher). Fixes: QTBUG-117996 Pick-to: 6.7 Change-Id: I8f3ce163ccc5408cac39fffd178d682e5bfa6955 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
parent
3fc5ee5c2e
commit
1ed0dd88a3
@ -282,8 +282,12 @@ void *QThreadPrivate::start(void *arg)
|
|||||||
#ifdef PTHREAD_CANCEL_DISABLE
|
#ifdef PTHREAD_CANCEL_DISABLE
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr);
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr);
|
||||||
#endif
|
#endif
|
||||||
pthread_cleanup_push(QThreadPrivate::finish, arg);
|
#if !defined(Q_OS_QNX)
|
||||||
|
// On QNX, calling finish() from a thread_local destructor causes the C
|
||||||
|
// library to hang.
|
||||||
|
static thread_local
|
||||||
|
#endif
|
||||||
|
auto cleanup = qScopeGuard([=] { finish(arg); });
|
||||||
terminate_on_exception([&] {
|
terminate_on_exception([&] {
|
||||||
QThread *thr = reinterpret_cast<QThread *>(arg);
|
QThread *thr = reinterpret_cast<QThread *>(arg);
|
||||||
QThreadData *data = QThreadData::get2(thr);
|
QThreadData *data = QThreadData::get2(thr);
|
||||||
@ -328,11 +332,7 @@ void *QThreadPrivate::start(void *arg)
|
|||||||
thr->run();
|
thr->run();
|
||||||
});
|
});
|
||||||
|
|
||||||
// This pop runs finish() below. It's outside the try/catch (and has its
|
// The qScopeGuard above call runs finish() below.
|
||||||
// own try/catch) to prevent finish() to be run in case an exception is
|
|
||||||
// thrown.
|
|
||||||
pthread_cleanup_pop(1);
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1299,8 +1299,16 @@ public:
|
|||||||
}
|
}
|
||||||
void registerSocketNotifier(QSocketNotifier *) override {}
|
void registerSocketNotifier(QSocketNotifier *) override {}
|
||||||
void unregisterSocketNotifier(QSocketNotifier *) override {}
|
void unregisterSocketNotifier(QSocketNotifier *) override {}
|
||||||
void registerTimer(Qt::TimerId, Duration, Qt::TimerType, QObject *) override {}
|
void registerTimer(Qt::TimerId id, Duration, Qt::TimerType, QObject *) override
|
||||||
bool unregisterTimer(Qt::TimerId) override { return false; }
|
{
|
||||||
|
if (registeredTimerId <= Qt::TimerId::Invalid)
|
||||||
|
registeredTimerId = id;
|
||||||
|
}
|
||||||
|
bool unregisterTimer(Qt::TimerId id) override
|
||||||
|
{
|
||||||
|
Qt::TimerId oldId = std::exchange(registeredTimerId, Qt::TimerId::Invalid);
|
||||||
|
return id == oldId;
|
||||||
|
}
|
||||||
bool unregisterTimers(QObject *) override { return false; }
|
bool unregisterTimers(QObject *) override { return false; }
|
||||||
QList<TimerInfoV2> timersForObject(QObject *) const override { return {}; }
|
QList<TimerInfoV2> timersForObject(QObject *) const override { return {}; }
|
||||||
Duration remainingTime(Qt::TimerId) const override { return 0s; }
|
Duration remainingTime(Qt::TimerId) const override { return 0s; }
|
||||||
@ -1313,25 +1321,47 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QBasicAtomicInt visited; // bool
|
QBasicAtomicInt visited; // bool
|
||||||
|
Qt::TimerId registeredTimerId = Qt::TimerId::Invalid;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ThreadObj : public QObject
|
struct ThreadLocalContent
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
static inline const QMetaObject *atStart;
|
||||||
public slots:
|
static inline const QMetaObject *atEnd;
|
||||||
void visit() {
|
QSemaphore *sem;
|
||||||
emit visited();
|
QBasicTimer timer;
|
||||||
|
|
||||||
|
ThreadLocalContent(QObject *obj, QSemaphore *sem)
|
||||||
|
: sem(sem)
|
||||||
|
{
|
||||||
|
ensureEventDispatcher();
|
||||||
|
atStart = QAbstractEventDispatcher::instance()->metaObject();
|
||||||
|
timer.start(10s, obj);
|
||||||
|
}
|
||||||
|
~ThreadLocalContent()
|
||||||
|
{
|
||||||
|
ensureEventDispatcher();
|
||||||
|
atEnd = QAbstractEventDispatcher::instance()->metaObject();
|
||||||
|
timer.stop();
|
||||||
|
sem->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensureEventDispatcher()
|
||||||
|
{
|
||||||
|
// QEventLoop's constructor has a call to QThreadData::ensureEventDispatcher()
|
||||||
|
QEventLoop dummy;
|
||||||
}
|
}
|
||||||
signals:
|
|
||||||
void visited();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_QThread::customEventDispatcher()
|
void tst_QThread::customEventDispatcher()
|
||||||
{
|
{
|
||||||
|
ThreadLocalContent::atStart = ThreadLocalContent::atEnd = nullptr;
|
||||||
|
|
||||||
QThread thr;
|
QThread thr;
|
||||||
// there should be no ED yet
|
// there should be no ED yet
|
||||||
QVERIFY(!thr.eventDispatcher());
|
QVERIFY(!thr.eventDispatcher());
|
||||||
DummyEventDispatcher *ed = new DummyEventDispatcher;
|
DummyEventDispatcher *ed = new DummyEventDispatcher;
|
||||||
|
QPointer<DummyEventDispatcher> weak_ed(ed);
|
||||||
thr.setEventDispatcher(ed);
|
thr.setEventDispatcher(ed);
|
||||||
// the new ED should be set
|
// the new ED should be set
|
||||||
QCOMPARE(thr.eventDispatcher(), ed);
|
QCOMPARE(thr.eventDispatcher(), ed);
|
||||||
@ -1340,25 +1370,39 @@ void tst_QThread::customEventDispatcher()
|
|||||||
thr.start();
|
thr.start();
|
||||||
// start() should not overwrite the ED
|
// start() should not overwrite the ED
|
||||||
QCOMPARE(thr.eventDispatcher(), ed);
|
QCOMPARE(thr.eventDispatcher(), ed);
|
||||||
|
QVERIFY(!weak_ed.isNull());
|
||||||
|
|
||||||
ThreadObj obj;
|
QObject obj;
|
||||||
obj.moveToThread(&thr);
|
obj.moveToThread(&thr);
|
||||||
// move was successful?
|
// move was successful?
|
||||||
QCOMPARE(obj.thread(), &thr);
|
QCOMPARE(obj.thread(), &thr);
|
||||||
QEventLoop loop;
|
|
||||||
connect(&obj, SIGNAL(visited()), &loop, SLOT(quit()), Qt::QueuedConnection);
|
QSemaphore threadLocalSemaphore;
|
||||||
QMetaObject::invokeMethod(&obj, "visit", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(&obj, [&]() {
|
||||||
loop.exec();
|
#ifndef Q_OS_WIN
|
||||||
|
// On Windows, the thread_locals are unsequenced between DLLs, so this
|
||||||
|
// could run after QThreadPrivate::finish()
|
||||||
|
static thread_local
|
||||||
|
#endif
|
||||||
|
ThreadLocalContent d(&obj, &threadLocalSemaphore);
|
||||||
|
}, Qt::BlockingQueuedConnection);
|
||||||
|
|
||||||
// test that the ED has really been used
|
// test that the ED has really been used
|
||||||
QVERIFY(ed->visited.loadRelaxed());
|
QVERIFY(ed->visited.loadRelaxed());
|
||||||
|
// and it's ours
|
||||||
|
QCOMPARE(ThreadLocalContent::atStart->className(), "DummyEventDispatcher");
|
||||||
|
|
||||||
QPointer<DummyEventDispatcher> weak_ed(ed);
|
|
||||||
QVERIFY(!weak_ed.isNull());
|
QVERIFY(!weak_ed.isNull());
|
||||||
thr.quit();
|
thr.quit();
|
||||||
|
|
||||||
// wait for thread to be stopped
|
// wait for thread to be stopped
|
||||||
QVERIFY(thr.wait(30000));
|
QVERIFY(thr.wait(30000));
|
||||||
|
QVERIFY(threadLocalSemaphore.tryAcquire(1, 30s));
|
||||||
|
|
||||||
// test that ED has been deleted
|
// test that ED has been deleted
|
||||||
QVERIFY(weak_ed.isNull());
|
QVERIFY(weak_ed.isNull());
|
||||||
|
// test that ED was ours
|
||||||
|
QCOMPARE(ThreadLocalContent::atEnd->className(), "DummyEventDispatcher");
|
||||||
}
|
}
|
||||||
|
|
||||||
class Job : public QObject
|
class Job : public QObject
|
||||||
|
Loading…
x
Reference in New Issue
Block a user