Make QObjectPrivate::threadData a proper atomic

QObjectPrivate::threadData used to be a QThreadData *, and was
read and written from multiple threads without proper synchronization.
As an example, it was read from QCoreApplication::postEvent and
written from QObject::moveToThread, therefore causing UB.

Port threadData to a proper atomic, removing the races. Fix all usage
points.

In general, QObject is documented to be simply reentrant,
not thread-safe, and certain bits (e.g. timers, moveToThread)
are not even reentrant. The reasoning therefore is that a given
QObject's threadData is not supposed to be touched by multiple
threads without some synchronization happening elsewhere, and
therefore relaxed loads should be sufficient.

As drive-by change: refactor QCoreApplication::postEvent.
It was particularly subtle, because it had a loop using a volatile
to cope with the possibility of the receiver object switching thread
while we tried to lock its thread's event queue.

However, volatile does not achieve any synchronization, so drop it,
and refactor the algorithm using better locking primitives.
Put this algorithm in a common place, and also reuse it from
removePostedEvents, which was lacking any synchronization.

Change-Id: Icc755f7eb418ff54b33db4bdd87fd8eaf4e82c7a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2019-10-11 00:42:08 +02:00
parent 4e0d5498eb
commit 782df5b41d
18 changed files with 165 additions and 126 deletions

View File

@ -246,7 +246,7 @@ bool QProcessPrivate::openChannel(Channel &channel)
return false; return false;
// create the socket notifiers // create the socket notifiers
if (threadData->hasEventDispatcher()) { if (threadData.loadRelaxed()->hasEventDispatcher()) {
if (&channel == &stdinChannel) { if (&channel == &stdinChannel) {
channel.notifier = new QSocketNotifier(channel.pipe[1], channel.notifier = new QSocketNotifier(channel.pipe[1],
QSocketNotifier::Write, q); QSocketNotifier::Write, q);
@ -377,7 +377,7 @@ void QProcessPrivate::startProcess()
return; return;
} }
if (threadData->hasEventDispatcher()) { if (threadData.loadRelaxed()->hasEventDispatcher()) {
startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], startupSocketNotifier = new QSocketNotifier(childStartedPipe[0],
QSocketNotifier::Read, q); QSocketNotifier::Read, q);
QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), QObject::connect(startupSocketNotifier, SIGNAL(activated(int)),
@ -517,7 +517,7 @@ void QProcessPrivate::startProcess()
if (stderrChannel.pipe[0] != -1) if (stderrChannel.pipe[0] != -1)
::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK);
if (threadData->eventDispatcher.loadAcquire()) { if (threadData.loadRelaxed()->eventDispatcher.loadAcquire()) {
deathNotifier = new QSocketNotifier(forkfd, QSocketNotifier::Read, q); deathNotifier = new QSocketNotifier(forkfd, QSocketNotifier::Read, q);
QObject::connect(deathNotifier, SIGNAL(activated(int)), QObject::connect(deathNotifier, SIGNAL(activated(int)),
q, SLOT(_q_processDied())); q, SLOT(_q_processDied()));

View File

@ -590,7 +590,7 @@ void QProcessPrivate::startProcess()
if (!pid) if (!pid)
return; return;
if (threadData->hasEventDispatcher()) { if (threadData.loadRelaxed()->hasEventDispatcher()) {
processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q); processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);
QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied())); QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied()));
processFinishedNotifier->setEnabled(true); processFinishedNotifier->setEnabled(true);

View File

@ -135,23 +135,6 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
#ifndef QT_NO_QOBJECT
class QMutexUnlocker
{
public:
inline explicit QMutexUnlocker(QMutex *m)
: mtx(m)
{ }
inline ~QMutexUnlocker() { unlock(); }
inline void unlock() { if (mtx) mtx->unlock(); mtx = 0; }
private:
Q_DISABLE_COPY(QMutexUnlocker)
QMutex *mtx;
};
#endif
#if defined(Q_OS_WIN) || defined(Q_OS_MAC) #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
extern QString qAppFileName(); extern QString qAppFileName();
#endif #endif
@ -517,25 +500,27 @@ QCoreApplicationPrivate::~QCoreApplicationPrivate()
void QCoreApplicationPrivate::cleanupThreadData() void QCoreApplicationPrivate::cleanupThreadData()
{ {
if (threadData && !threadData_clean) { auto thisThreadData = threadData.loadRelaxed();
if (thisThreadData && !threadData_clean) {
#if QT_CONFIG(thread) #if QT_CONFIG(thread)
void *data = &threadData->tls; void *data = &thisThreadData->tls;
QThreadStorageData::finish((void **)data); QThreadStorageData::finish((void **)data);
#endif #endif
// need to clear the state of the mainData, just in case a new QCoreApplication comes along. // need to clear the state of the mainData, just in case a new QCoreApplication comes along.
const auto locker = qt_scoped_lock(threadData->postEventList.mutex); const auto locker = qt_scoped_lock(thisThreadData->postEventList.mutex);
for (int i = 0; i < threadData->postEventList.size(); ++i) { for (int i = 0; i < thisThreadData->postEventList.size(); ++i) {
const QPostEvent &pe = threadData->postEventList.at(i); const QPostEvent &pe = thisThreadData->postEventList.at(i);
if (pe.event) { if (pe.event) {
--pe.receiver->d_func()->postedEvents; --pe.receiver->d_func()->postedEvents;
pe.event->posted = false; pe.event->posted = false;
delete pe.event; delete pe.event;
} }
} }
threadData->postEventList.clear(); thisThreadData->postEventList.clear();
threadData->postEventList.recursion = 0; thisThreadData->postEventList.recursion = 0;
threadData->quitNow = false; thisThreadData->quitNow = false;
threadData_clean = true; threadData_clean = true;
} }
} }
@ -858,7 +843,8 @@ void QCoreApplicationPrivate::init()
#ifndef QT_NO_QOBJECT #ifndef QT_NO_QOBJECT
// use the event dispatcher created by the app programmer (if any) // use the event dispatcher created by the app programmer (if any)
Q_ASSERT(!eventDispatcher); Q_ASSERT(!eventDispatcher);
eventDispatcher = threadData->eventDispatcher.loadRelaxed(); auto thisThreadData = threadData.loadRelaxed();
eventDispatcher = thisThreadData->eventDispatcher.loadRelaxed();
// otherwise we create one // otherwise we create one
if (!eventDispatcher) if (!eventDispatcher)
@ -866,11 +852,11 @@ void QCoreApplicationPrivate::init()
Q_ASSERT(eventDispatcher); Q_ASSERT(eventDispatcher);
if (!eventDispatcher->parent()) { if (!eventDispatcher->parent()) {
eventDispatcher->moveToThread(threadData->thread.loadAcquire()); eventDispatcher->moveToThread(thisThreadData->thread.loadAcquire());
eventDispatcher->setParent(q); eventDispatcher->setParent(q);
} }
threadData->eventDispatcher = eventDispatcher; thisThreadData->eventDispatcher = eventDispatcher;
eventDispatcherReady(); eventDispatcherReady();
#endif #endif
@ -914,7 +900,7 @@ QCoreApplication::~QCoreApplication()
#endif #endif
#ifndef QT_NO_QOBJECT #ifndef QT_NO_QOBJECT
d_func()->threadData->eventDispatcher = nullptr; d_func()->threadData.loadRelaxed()->eventDispatcher = nullptr;
if (QCoreApplicationPrivate::eventDispatcher) if (QCoreApplicationPrivate::eventDispatcher)
QCoreApplicationPrivate::eventDispatcher->closingDown(); QCoreApplicationPrivate::eventDispatcher->closingDown();
QCoreApplicationPrivate::eventDispatcher = nullptr; QCoreApplicationPrivate::eventDispatcher = nullptr;
@ -1185,7 +1171,7 @@ static bool doNotify(QObject *receiver, QEvent *event)
bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event) bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event)
{ {
// We can't access the application event filters outside of the main thread (race conditions) // We can't access the application event filters outside of the main thread (race conditions)
Q_ASSERT(receiver->d_func()->threadData->thread.loadAcquire() == mainThread()); Q_ASSERT(receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread());
if (extraData) { if (extraData) {
// application event filters are only called for objects in the GUI thread // application event filters are only called for objects in the GUI thread
@ -1238,7 +1224,7 @@ bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
// send to all application event filters (only does anything in the main thread) // send to all application event filters (only does anything in the main thread)
if (QCoreApplication::self if (QCoreApplication::self
&& receiver->d_func()->threadData->thread.loadAcquire() == mainThread() && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) { && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
filtered = true; filtered = true;
return filtered; return filtered;
@ -1414,7 +1400,7 @@ int QCoreApplication::exec()
void QCoreApplicationPrivate::execCleanup() void QCoreApplicationPrivate::execCleanup()
{ {
threadData->quitNow = false; threadData.loadRelaxed()->quitNow = false;
in_exec = false; in_exec = false;
if (!aboutToQuitEmitted) if (!aboutToQuitEmitted)
emit q_func()->aboutToQuit(QCoreApplication::QPrivateSignal()); emit q_func()->aboutToQuit(QCoreApplication::QPrivateSignal());
@ -1451,7 +1437,7 @@ void QCoreApplication::exit(int returnCode)
{ {
if (!self) if (!self)
return; return;
QThreadData *data = self->d_func()->threadData; QThreadData *data = self->d_func()->threadData.loadRelaxed();
data->quitNow = true; data->quitNow = true;
for (int i = 0; i < data->eventLoops.size(); ++i) { for (int i = 0; i < data->eventLoops.size(); ++i) {
QEventLoop *eventLoop = data->eventLoops.at(i); QEventLoop *eventLoop = data->eventLoops.at(i);
@ -1501,6 +1487,38 @@ bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
#endif // QT_NO_QOBJECT #endif // QT_NO_QOBJECT
QCoreApplicationPrivate::QPostEventListLocker QCoreApplicationPrivate::lockThreadPostEventList(QObject *object)
{
QPostEventListLocker locker;
if (!object) {
locker.threadData = QThreadData::current();
locker.locker = qt_unique_lock(locker.threadData->postEventList.mutex);
return locker;
}
auto &threadData = QObjectPrivate::get(object)->threadData;
// if object has moved to another thread, follow it
for (;;) {
// synchronizes with the storeRelease in QObject::moveToThread
locker.threadData = threadData.loadAcquire();
if (!locker.threadData) {
// destruction in progress
return locker;
}
auto temporaryLocker = qt_unique_lock(locker.threadData->postEventList.mutex);
if (locker.threadData == threadData.loadAcquire()) {
locker.locker = std::move(temporaryLocker);
break;
}
}
Q_ASSERT(locker.threadData);
return locker;
}
/*! /*!
\since 4.3 \since 4.3
@ -1536,32 +1554,14 @@ void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
return; return;
} }
QThreadData * volatile * pdata = &receiver->d_func()->threadData; auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
QThreadData *data = *pdata; if (!locker.threadData) {
if (!data) {
// posting during destruction? just delete the event to prevent a leak // posting during destruction? just delete the event to prevent a leak
delete event; delete event;
return; return;
} }
// lock the post event mutex QThreadData *data = locker.threadData;
data->postEventList.mutex.lock();
// if object has moved to another thread, follow it
while (data != *pdata) {
data->postEventList.mutex.unlock();
data = *pdata;
if (!data) {
// posting during destruction? just delete the event to prevent a leak
delete event;
return;
}
data->postEventList.mutex.lock();
}
QMutexUnlocker locker(&data->postEventList.mutex);
// if this is one of the compressible events, do compression // if this is one of the compressible events, do compression
if (receiver->d_func()->postedEvents if (receiver->d_func()->postedEvents
@ -1860,8 +1860,8 @@ void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type
void QCoreApplication::removePostedEvents(QObject *receiver, int eventType) void QCoreApplication::removePostedEvents(QObject *receiver, int eventType)
{ {
QThreadData *data = receiver ? receiver->d_func()->threadData : QThreadData::current(); auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
auto locker = qt_unique_lock(data->postEventList.mutex); QThreadData *data = locker.threadData;
// the QObject destructor calls this function directly. this can // the QObject destructor calls this function directly. this can
// happen while the event loop is in the middle of posting events, // happen while the event loop is in the middle of posting events,

View File

@ -61,6 +61,7 @@
#endif #endif
#ifndef QT_NO_QOBJECT #ifndef QT_NO_QOBJECT
#include "private/qobject_p.h" #include "private/qobject_p.h"
#include "private/qlocking_p.h"
#endif #endif
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -140,6 +141,15 @@ public:
static void checkReceiverThread(QObject *receiver); static void checkReceiverThread(QObject *receiver);
void cleanupThreadData(); void cleanupThreadData();
struct QPostEventListLocker
{
QThreadData *threadData;
std::unique_lock<QMutex> locker;
void unlock() { locker.unlock(); }
};
static QPostEventListLocker lockThreadPostEventList(QObject *object);
#endif // QT_NO_QOBJECT #endif // QT_NO_QOBJECT
int &argc; int &argc;

View File

@ -918,7 +918,7 @@ QDebug operator<<(QDebug dbg, const MSG &msg)
#ifndef QT_NO_QOBJECT #ifndef QT_NO_QOBJECT
void QCoreApplicationPrivate::removePostedTimerEvent(QObject *object, int timerId) void QCoreApplicationPrivate::removePostedTimerEvent(QObject *object, int timerId)
{ {
QThreadData *data = object->d_func()->threadData; QThreadData *data = object->d_func()->threadData.loadRelaxed();
const auto locker = qt_scoped_lock(data->postEventList.mutex); const auto locker = qt_scoped_lock(data->postEventList.mutex);
if (data->postEventList.size() == 0) if (data->postEventList.size() == 0)

View File

@ -463,13 +463,15 @@ bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
// we are awake, broadcast it // we are awake, broadcast it
emit awake(); emit awake();
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
auto threadData = d->threadData.loadRelaxed();
QCoreApplicationPrivate::sendPostedEvents(0, 0, threadData);
const bool include_timers = (flags & QEventLoop::X11ExcludeTimers) == 0; const bool include_timers = (flags & QEventLoop::X11ExcludeTimers) == 0;
const bool include_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0; const bool include_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0;
const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents; const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents;
const bool canWait = (d->threadData->canWaitLocked() const bool canWait = (threadData->canWaitLocked()
&& !d->interrupt.loadRelaxed() && !d->interrupt.loadRelaxed()
&& wait_for_events); && wait_for_events);

View File

@ -1048,7 +1048,7 @@ bool QEventDispatcherWin32::event(QEvent *e)
void QEventDispatcherWin32::sendPostedEvents() void QEventDispatcherWin32::sendPostedEvents()
{ {
Q_D(QEventDispatcherWin32); Q_D(QEventDispatcherWin32);
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed());
} }
HWND QEventDispatcherWin32::internalHwnd() HWND QEventDispatcherWin32::internalHwnd()

View File

@ -106,7 +106,7 @@ QEventLoop::QEventLoop(QObject *parent)
if (!QCoreApplication::instance() && QCoreApplicationPrivate::threadRequiresCoreApplication()) { if (!QCoreApplication::instance() && QCoreApplicationPrivate::threadRequiresCoreApplication()) {
qWarning("QEventLoop: Cannot be used without QApplication"); qWarning("QEventLoop: Cannot be used without QApplication");
} else { } else {
d->threadData->ensureEventDispatcher(); d->threadData.loadRelaxed()->ensureEventDispatcher();
} }
} }
@ -133,9 +133,10 @@ QEventLoop::~QEventLoop()
bool QEventLoop::processEvents(ProcessEventsFlags flags) bool QEventLoop::processEvents(ProcessEventsFlags flags)
{ {
Q_D(QEventLoop); Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher()) auto threadData = d->threadData.loadRelaxed();
if (!threadData->hasEventDispatcher())
return false; return false;
return d->threadData->eventDispatcher.loadRelaxed()->processEvents(flags); return threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
} }
/*! /*!
@ -164,9 +165,11 @@ bool QEventLoop::processEvents(ProcessEventsFlags flags)
int QEventLoop::exec(ProcessEventsFlags flags) int QEventLoop::exec(ProcessEventsFlags flags)
{ {
Q_D(QEventLoop); Q_D(QEventLoop);
auto threadData = d->threadData.loadRelaxed();
//we need to protect from race condition with QThread::exit //we need to protect from race condition with QThread::exit
QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread.loadAcquire()))->mutex); QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);
if (d->threadData->quitNow) if (threadData->quitNow)
return -1; return -1;
if (d->inExec) { if (d->inExec) {
@ -183,8 +186,11 @@ int QEventLoop::exec(ProcessEventsFlags flags)
{ {
d->inExec = true; d->inExec = true;
d->exit.storeRelease(false); d->exit.storeRelease(false);
++d->threadData->loopLevel;
d->threadData->eventLoops.push(d->q_func()); auto threadData = d->threadData.loadRelaxed();
++threadData->loopLevel;
threadData->eventLoops.push(d->q_func());
locker.unlock(); locker.unlock();
} }
@ -198,11 +204,12 @@ int QEventLoop::exec(ProcessEventsFlags flags)
"QCoreApplication::notify() and catch all exceptions there.\n"); "QCoreApplication::notify() and catch all exceptions there.\n");
} }
locker.relock(); locker.relock();
QEventLoop *eventLoop = d->threadData->eventLoops.pop(); auto threadData = d->threadData.loadRelaxed();
QEventLoop *eventLoop = threadData->eventLoops.pop();
Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error"); Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
Q_UNUSED(eventLoop); // --release warning Q_UNUSED(eventLoop); // --release warning
d->inExec = false; d->inExec = false;
--d->threadData->loopLevel; --threadData->loopLevel;
} }
}; };
LoopReference ref(d, locker); LoopReference ref(d, locker);
@ -217,7 +224,7 @@ int QEventLoop::exec(ProcessEventsFlags flags)
// exception, which returns control to the browser while preserving the C++ stack. // exception, which returns control to the browser while preserving the C++ stack.
// Event processing then continues as normal. The sleep call below never returns. // Event processing then continues as normal. The sleep call below never returns.
// QTBUG-70185 // QTBUG-70185
if (d->threadData->loopLevel > 1) if (threadData->loopLevel > 1)
emscripten_sleep(1); emscripten_sleep(1);
#endif #endif
@ -247,7 +254,7 @@ int QEventLoop::exec(ProcessEventsFlags flags)
void QEventLoop::processEvents(ProcessEventsFlags flags, int maxTime) void QEventLoop::processEvents(ProcessEventsFlags flags, int maxTime)
{ {
Q_D(QEventLoop); Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher()) if (!d->threadData.loadRelaxed()->hasEventDispatcher())
return; return;
QElapsedTimer start; QElapsedTimer start;
@ -276,21 +283,22 @@ void QEventLoop::processEvents(ProcessEventsFlags flags, int maxTime)
void QEventLoop::exit(int returnCode) void QEventLoop::exit(int returnCode)
{ {
Q_D(QEventLoop); Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher()) auto threadData = d->threadData.loadAcquire();
if (!threadData->hasEventDispatcher())
return; return;
d->returnCode.storeRelaxed(returnCode); d->returnCode.storeRelaxed(returnCode);
d->exit.storeRelease(true); d->exit.storeRelease(true);
d->threadData->eventDispatcher.loadRelaxed()->interrupt(); threadData->eventDispatcher.loadRelaxed()->interrupt();
#ifdef Q_OS_WASM #ifdef Q_OS_WASM
// QEventLoop::exec() never returns in emscripten. We implement approximate behavior here. // QEventLoop::exec() never returns in emscripten. We implement approximate behavior here.
// QTBUG-70185 // QTBUG-70185
if (d->threadData->loopLevel == 1) { if (threadData->loopLevel == 1) {
emscripten_force_exit(returnCode); emscripten_force_exit(returnCode);
} else { } else {
d->inExec = false; d->inExec = false;
--d->threadData->loopLevel; --threadData->loopLevel;
} }
#endif #endif
} }
@ -316,9 +324,10 @@ bool QEventLoop::isRunning() const
void QEventLoop::wakeUp() void QEventLoop::wakeUp()
{ {
Q_D(QEventLoop); Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher()) auto threadData = d->threadData.loadAcquire();
if (!threadData->hasEventDispatcher())
return; return;
d->threadData->eventDispatcher.loadRelaxed()->wakeUp(); threadData->eventDispatcher.loadRelaxed()->wakeUp();
} }

View File

@ -212,11 +212,12 @@ QObjectPrivate::QObjectPrivate(int version)
QObjectPrivate::~QObjectPrivate() QObjectPrivate::~QObjectPrivate()
{ {
auto thisThreadData = threadData.loadRelaxed();
if (extraData && !extraData->runningTimers.isEmpty()) { if (extraData && !extraData->runningTimers.isEmpty()) {
if (Q_LIKELY(threadData->thread.loadAcquire() == QThread::currentThread())) { if (Q_LIKELY(thisThreadData->thread.loadAcquire() == QThread::currentThread())) {
// unregister pending timers // unregister pending timers
if (threadData->hasEventDispatcher()) if (thisThreadData->hasEventDispatcher())
threadData->eventDispatcher.loadRelaxed()->unregisterTimers(q_ptr); thisThreadData->eventDispatcher.loadRelaxed()->unregisterTimers(q_ptr);
// release the timer ids back to the pool // release the timer ids back to the pool
for (int i = 0; i < extraData->runningTimers.size(); ++i) for (int i = 0; i < extraData->runningTimers.size(); ++i)
@ -229,7 +230,7 @@ QObjectPrivate::~QObjectPrivate()
if (postedEvents) if (postedEvents)
QCoreApplication::removePostedEvents(q_ptr, 0); QCoreApplication::removePostedEvents(q_ptr, 0);
threadData->deref(); thisThreadData->deref();
if (metaObject) metaObject->objectDestroyed(q_ptr); if (metaObject) metaObject->objectDestroyed(q_ptr);
@ -920,11 +921,12 @@ QObject::QObject(QObjectPrivate &dd, QObject *parent)
Q_D(QObject); Q_D(QObject);
d_ptr->q_ptr = this; d_ptr->q_ptr = this;
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current(); auto threadData = (parent && !parent->thread()) ? parent->d_func()->threadData.loadRelaxed() : QThreadData::current();
d->threadData->ref(); threadData->ref();
d->threadData.storeRelaxed(threadData);
if (parent) { if (parent) {
QT_TRY { QT_TRY {
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData)) if (!check_parent_thread(parent, parent ? parent->d_func()->threadData.loadRelaxed() : 0, threadData))
parent = 0; parent = 0;
if (d->isWidget) { if (d->isWidget) {
if (parent) { if (parent) {
@ -936,7 +938,7 @@ QObject::QObject(QObjectPrivate &dd, QObject *parent)
setParent(parent); setParent(parent);
} }
} QT_CATCH(...) { } QT_CATCH(...) {
d->threadData->deref(); threadData->deref();
QT_RETHROW; QT_RETHROW;
} }
} }
@ -1320,7 +1322,7 @@ bool QObject::event(QEvent *e)
case QEvent::ThreadChange: { case QEvent::ThreadChange: {
Q_D(QObject); Q_D(QObject);
QThreadData *threadData = d->threadData; QThreadData *threadData = d->threadData.loadRelaxed();
QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.loadRelaxed(); QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.loadRelaxed();
if (eventDispatcher) { if (eventDispatcher) {
QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this); QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
@ -1487,7 +1489,7 @@ bool QObject::blockSignals(bool block) noexcept
*/ */
QThread *QObject::thread() const QThread *QObject::thread() const
{ {
return d_func()->threadData->thread.loadAcquire(); return d_func()->threadData.loadRelaxed()->thread.loadAcquire();
} }
/*! /*!
@ -1534,7 +1536,7 @@ void QObject::moveToThread(QThread *targetThread)
{ {
Q_D(QObject); Q_D(QObject);
if (d->threadData->thread.loadAcquire() == targetThread) { if (d->threadData.loadRelaxed()->thread.loadAcquire() == targetThread) {
// object is already in this thread // object is already in this thread
return; return;
} }
@ -1550,13 +1552,14 @@ void QObject::moveToThread(QThread *targetThread)
QThreadData *currentData = QThreadData::current(); QThreadData *currentData = QThreadData::current();
QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : nullptr; QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : nullptr;
if (d->threadData->thread.loadAcquire() == 0 && currentData == targetData) { QThreadData *thisThreadData = d->threadData.loadRelaxed();
if (!thisThreadData->thread.loadAcquire() && currentData == targetData) {
// one exception to the rule: we allow moving objects with no thread affinity to the current thread // one exception to the rule: we allow moving objects with no thread affinity to the current thread
currentData = d->threadData; currentData = d->threadData;
} else if (d->threadData != currentData) { } else if (thisThreadData != currentData) {
qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p).\n" qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p).\n"
"Cannot move to target thread (%p)\n", "Cannot move to target thread (%p)\n",
currentData->thread.loadRelaxed(), d->threadData->thread.loadRelaxed(), targetData ? targetData->thread.loadRelaxed() : nullptr); currentData->thread.loadRelaxed(), thisThreadData->thread.loadRelaxed(), targetData ? targetData->thread.loadRelaxed() : nullptr);
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
qWarning("You might be loading two sets of Qt binaries into the same process. " qWarning("You might be loading two sets of Qt binaries into the same process. "
@ -1653,8 +1656,10 @@ void QObjectPrivate::setThreadData_helper(QThreadData *currentData, QThreadData
// set new thread data // set new thread data
targetData->ref(); targetData->ref();
threadData->deref(); threadData.loadRelaxed()->deref();
threadData = targetData;
// synchronizes with loadAcquire e.g. in QCoreApplication::postEvent
threadData.storeRelease(targetData);
for (int i = 0; i < children.size(); ++i) { for (int i = 0; i < children.size(); ++i) {
QObject *child = children.at(i); QObject *child = children.at(i);
@ -1666,7 +1671,7 @@ void QObjectPrivate::_q_reregisterTimers(void *pointer)
{ {
Q_Q(QObject); Q_Q(QObject);
QList<QAbstractEventDispatcher::TimerInfo> *timerList = reinterpret_cast<QList<QAbstractEventDispatcher::TimerInfo> *>(pointer); QList<QAbstractEventDispatcher::TimerInfo> *timerList = reinterpret_cast<QList<QAbstractEventDispatcher::TimerInfo> *>(pointer);
QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.loadRelaxed(); QAbstractEventDispatcher *eventDispatcher = threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
for (int i = 0; i < timerList->size(); ++i) { for (int i = 0; i < timerList->size(); ++i) {
const QAbstractEventDispatcher::TimerInfo &ti = timerList->at(i); const QAbstractEventDispatcher::TimerInfo &ti = timerList->at(i);
eventDispatcher->registerTimer(ti.timerId, ti.interval, ti.timerType, q); eventDispatcher->registerTimer(ti.timerId, ti.interval, ti.timerType, q);
@ -1724,7 +1729,9 @@ int QObject::startTimer(int interval, Qt::TimerType timerType)
qWarning("QObject::startTimer: Timers cannot have negative intervals"); qWarning("QObject::startTimer: Timers cannot have negative intervals");
return 0; return 0;
} }
if (Q_UNLIKELY(!d->threadData->hasEventDispatcher())) {
auto thisThreadData = d->threadData.loadRelaxed();
if (Q_UNLIKELY(!thisThreadData->hasEventDispatcher())) {
qWarning("QObject::startTimer: Timers can only be used with threads started with QThread"); qWarning("QObject::startTimer: Timers can only be used with threads started with QThread");
return 0; return 0;
} }
@ -1732,7 +1739,7 @@ int QObject::startTimer(int interval, Qt::TimerType timerType)
qWarning("QObject::startTimer: Timers cannot be started from another thread"); qWarning("QObject::startTimer: Timers cannot be started from another thread");
return 0; return 0;
} }
int timerId = d->threadData->eventDispatcher.loadRelaxed()->registerTimer(interval, timerType, this); int timerId = thisThreadData->eventDispatcher.loadRelaxed()->registerTimer(interval, timerType, this);
if (!d->extraData) if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData; d->extraData = new QObjectPrivate::ExtraData;
d->extraData->runningTimers.append(timerId); d->extraData->runningTimers.append(timerId);
@ -1806,8 +1813,9 @@ void QObject::killTimer(int id)
return; return;
} }
if (d->threadData->hasEventDispatcher()) auto thisThreadData = d->threadData.loadRelaxed();
d->threadData->eventDispatcher.loadRelaxed()->unregisterTimer(id); if (thisThreadData->hasEventDispatcher())
thisThreadData->eventDispatcher.loadRelaxed()->unregisterTimer(id);
d->extraData->runningTimers.remove(at); d->extraData->runningTimers.remove(at);
QAbstractEventDispatcherPrivate::releaseTimerId(id); QAbstractEventDispatcherPrivate::releaseTimerId(id);
@ -3774,7 +3782,7 @@ void doActivate(QObject *sender, int signal_index, void **argv)
list = &signalVector->at(-1); list = &signalVector->at(-1);
Qt::HANDLE currentThreadId = QThread::currentThreadId(); Qt::HANDLE currentThreadId = QThread::currentThreadId();
bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData->threadId.loadRelaxed(); bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData.loadRelaxed()->threadId.loadRelaxed();
// We need to check against the highest connection id to ensure that signals added // We need to check against the highest connection id to ensure that signals added
// during the signal emission are not emitted in this emission. // during the signal emission are not emitted in this emission.

View File

@ -374,8 +374,13 @@ public:
} }
public: public:
ExtraData *extraData; // extra data set by the user ExtraData *extraData; // extra data set by the user
QThreadData *getThreadData() const { return threadData; } QThreadData *getThreadData() const { return threadData.loadAcquire(); }
QThreadData *threadData; // id of the thread that owns the object // This atomic requires acquire/release semantics in a few places,
// e.g. QObject::moveToThread must synchronize with QCoreApplication::postEvent,
// because postEvent is thread-safe.
// However, most of the code paths involving QObject are only reentrant and
// not thread-safe, so synchronization should not be necessary there.
QAtomicPointer<QThreadData> threadData; // id of the thread that owns the object
using ConnectionDataPointer = QExplicitlySharedDataPointer<ConnectionData>; using ConnectionDataPointer = QExplicitlySharedDataPointer<ConnectionData>;
QAtomicPointer<ConnectionData> connections; QAtomicPointer<ConnectionData> connections;

View File

@ -147,12 +147,14 @@ QSocketNotifier::QSocketNotifier(qintptr socket, Type type, QObject *parent)
d->sntype = type; d->sntype = type;
d->snenabled = true; d->snenabled = true;
auto thisThreadData = d->threadData.loadRelaxed();
if (socket < 0) if (socket < 0)
qWarning("QSocketNotifier: Invalid socket specified"); qWarning("QSocketNotifier: Invalid socket specified");
else if (!d->threadData->hasEventDispatcher()) else if (!thisThreadData->hasEventDispatcher())
qWarning("QSocketNotifier: Can only be used with threads started with QThread"); qWarning("QSocketNotifier: Can only be used with threads started with QThread");
else else
d->threadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this); thisThreadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
} }
/*! /*!
@ -234,16 +236,19 @@ void QSocketNotifier::setEnabled(bool enable)
return; return;
d->snenabled = enable; d->snenabled = enable;
if (!d->threadData->hasEventDispatcher()) // perhaps application/thread is shutting down
auto thisThreadData = d->threadData.loadRelaxed();
if (!thisThreadData->hasEventDispatcher()) // perhaps application/thread is shutting down
return; return;
if (Q_UNLIKELY(thread() != QThread::currentThread())) { if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"); qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
return; return;
} }
if (d->snenabled) if (d->snenabled)
d->threadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this); thisThreadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
else else
d->threadData->eventDispatcher.loadRelaxed()->unregisterSocketNotifier(this); thisThreadData->eventDispatcher.loadRelaxed()->unregisterSocketNotifier(this);
} }

View File

@ -124,7 +124,7 @@ QWinEventNotifier::QWinEventNotifier(HANDLE hEvent, QObject *parent)
: QObject(*new QWinEventNotifierPrivate(hEvent, false), parent) : QObject(*new QWinEventNotifierPrivate(hEvent, false), parent)
{ {
Q_D(QWinEventNotifier); Q_D(QWinEventNotifier);
QAbstractEventDispatcher *eventDispatcher = d->threadData->eventDispatcher.loadRelaxed(); QAbstractEventDispatcher *eventDispatcher = d->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
if (Q_UNLIKELY(!eventDispatcher)) { if (Q_UNLIKELY(!eventDispatcher)) {
qWarning("QWinEventNotifier: Can only be used with threads started with QThread"); qWarning("QWinEventNotifier: Can only be used with threads started with QThread");
return; return;
@ -197,7 +197,7 @@ void QWinEventNotifier::setEnabled(bool enable)
return; return;
d->enabled = enable; d->enabled = enable;
QAbstractEventDispatcher *eventDispatcher = d->threadData->eventDispatcher.loadRelaxed(); QAbstractEventDispatcher *eventDispatcher = d->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
if (!eventDispatcher) { // perhaps application is shutting down if (!eventDispatcher) { // perhaps application is shutting down
if (!enable && d->waitHandle != nullptr) if (!enable && d->waitHandle != nullptr)
d->unregisterWaitObject(); d->unregisterWaitObject();
@ -256,7 +256,7 @@ void QWinEventNotifierPrivate::unregisterWaitObject()
static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/) static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/)
{ {
QWinEventNotifierPrivate *nd = reinterpret_cast<QWinEventNotifierPrivate *>(context); QWinEventNotifierPrivate *nd = reinterpret_cast<QWinEventNotifierPrivate *>(context);
QAbstractEventDispatcher *eventDispatcher = nd->threadData->eventDispatcher.loadRelaxed(); QAbstractEventDispatcher *eventDispatcher = nd->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
// Happens when Q(Core)Application is destroyed before QWinEventNotifier. // Happens when Q(Core)Application is destroyed before QWinEventNotifier.
// https://bugreports.qt.io/browse/QTBUG-70214 // https://bugreports.qt.io/browse/QTBUG-70214

View File

@ -116,7 +116,7 @@ public:
static QAbstractEventDispatcher *qt_qpa_core_dispatcher() static QAbstractEventDispatcher *qt_qpa_core_dispatcher()
{ {
if (QCoreApplication::instance()) if (QCoreApplication::instance())
return QCoreApplication::instance()->d_func()->threadData->eventDispatcher.loadRelaxed(); return QCoreApplication::instance()->d_func()->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
else else
return nullptr; return nullptr;
} }

View File

@ -659,7 +659,7 @@ bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtoc
configureCreatedSocket(); configureCreatedSocket();
if (threadData->hasEventDispatcher()) if (threadData.loadRelaxed()->hasEventDispatcher())
socketEngine->setReceiver(this); socketEngine->setReceiver(this);
#if defined (QABSTRACTSOCKET_DEBUG) #if defined (QABSTRACTSOCKET_DEBUG)
@ -1138,7 +1138,7 @@ void QAbstractSocketPrivate::_q_connectToNextAddress()
} }
// Start the connect timer. // Start the connect timer.
if (threadData->hasEventDispatcher()) { if (threadData.loadRelaxed()->hasEventDispatcher()) {
if (!connectTimer) { if (!connectTimer) {
connectTimer = new QTimer(q); connectTimer = new QTimer(q);
QObject::connect(connectTimer, SIGNAL(timeout()), QObject::connect(connectTimer, SIGNAL(timeout()),
@ -1740,7 +1740,7 @@ void QAbstractSocket::connectToHost(const QString &hostName, quint16 port,
return; return;
#endif #endif
} else { } else {
if (d->threadData->hasEventDispatcher()) { if (d->threadData.loadRelaxed()->hasEventDispatcher()) {
// this internal API for QHostInfo either immediately gives us the desired // this internal API for QHostInfo either immediately gives us the desired
// QHostInfo from cache or later calls the _q_startConnecting slot. // QHostInfo from cache or later calls the _q_startConnecting slot.
bool immediateResultValid = false; bool immediateResultValid = false;
@ -1953,7 +1953,7 @@ bool QAbstractSocket::setSocketDescriptor(qintptr socketDescriptor, SocketState
// Sync up with error string, which open() shall clear. // Sync up with error string, which open() shall clear.
d->socketError = UnknownSocketError; d->socketError = UnknownSocketError;
if (d->threadData->hasEventDispatcher()) if (d->threadData.loadRelaxed()->hasEventDispatcher())
d->socketEngine->setReceiver(d); d->socketEngine->setReceiver(d);
QIODevice::open(openMode); QIODevice::open(openMode);

View File

@ -1341,7 +1341,7 @@ void QNativeSocketEngine::setReadNotificationEnabled(bool enable)
Q_D(QNativeSocketEngine); Q_D(QNativeSocketEngine);
if (d->readNotifier) { if (d->readNotifier) {
d->readNotifier->setEnabled(enable); d->readNotifier->setEnabled(enable);
} else if (enable && d->threadData->hasEventDispatcher()) { } else if (enable && d->threadData.loadRelaxed()->hasEventDispatcher()) {
d->readNotifier = new QReadNotifier(d->socketDescriptor, this); d->readNotifier = new QReadNotifier(d->socketDescriptor, this);
d->readNotifier->setEnabled(true); d->readNotifier->setEnabled(true);
} }
@ -1358,7 +1358,7 @@ void QNativeSocketEngine::setWriteNotificationEnabled(bool enable)
Q_D(QNativeSocketEngine); Q_D(QNativeSocketEngine);
if (d->writeNotifier) { if (d->writeNotifier) {
d->writeNotifier->setEnabled(enable); d->writeNotifier->setEnabled(enable);
} else if (enable && d->threadData->hasEventDispatcher()) { } else if (enable && d->threadData.loadRelaxed()->hasEventDispatcher()) {
d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this); d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this);
d->writeNotifier->setEnabled(true); d->writeNotifier->setEnabled(true);
} }
@ -1375,7 +1375,7 @@ void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable)
Q_D(QNativeSocketEngine); Q_D(QNativeSocketEngine);
if (d->exceptNotifier) { if (d->exceptNotifier) {
d->exceptNotifier->setEnabled(enable); d->exceptNotifier->setEnabled(enable);
} else if (enable && d->threadData->hasEventDispatcher()) { } else if (enable && d->threadData.loadRelaxed()->hasEventDispatcher()) {
d->exceptNotifier = new QExceptionNotifier(d->socketDescriptor, this); d->exceptNotifier = new QExceptionNotifier(d->socketDescriptor, this);
d->exceptNotifier->setEnabled(true); d->exceptNotifier->setEnabled(true);
} }

View File

@ -149,7 +149,7 @@ QT_USE_NAMESPACE
if ([reflectionDelegate respondsToSelector:_cmd]) if ([reflectionDelegate respondsToSelector:_cmd])
return [reflectionDelegate applicationShouldTerminate:sender]; return [reflectionDelegate applicationShouldTerminate:sender];
if (QGuiApplicationPrivate::instance()->threadData->eventLoops.isEmpty()) { if (QGuiApplicationPrivate::instance()->threadData.loadRelaxed()->eventLoops.isEmpty()) {
// No event loop is executing. This probably means that Qt is used as a plugin, // No event loop is executing. This probably means that Qt is used as a plugin,
// or as a part of a native Cocoa application. In any case it should be fine to // or as a part of a native Cocoa application. In any case it should be fine to
// terminate now. // terminate now.
@ -359,7 +359,7 @@ QT_USE_NAMESPACE
if (!platformItem || platformItem->menu()) if (!platformItem || platformItem->menu())
return; return;
QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed());
QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]];
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);

View File

@ -511,7 +511,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
if (hadModalSession && !d->currentModalSessionCached) if (hadModalSession && !d->currentModalSessionCached)
interruptLater = true; interruptLater = true;
} }
bool canWait = (d->threadData->canWait bool canWait = (d->threadData.loadRelaxed()->canWait
&& !retVal && !retVal
&& !d->interrupt && !d->interrupt
&& (d->processEventsFlags & QEventLoop::WaitForMoreEvents)); && (d->processEventsFlags & QEventLoop::WaitForMoreEvents));
@ -878,7 +878,7 @@ void QCocoaEventDispatcherPrivate::processPostedEvents()
} }
int serial = serialNumber.loadRelaxed(); int serial = serialNumber.loadRelaxed();
if (!threadData->canWait || (serial != lastSerial)) { if (!threadData.loadRelaxed()->canWait || (serial != lastSerial)) {
lastSerial = serial; lastSerial = serial;
QCoreApplication::sendPostedEvents(); QCoreApplication::sendPostedEvents();
QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents); QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);

View File

@ -3660,7 +3660,7 @@ bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
// send to all application event filters // send to all application event filters
if (threadRequiresCoreApplication() if (threadRequiresCoreApplication()
&& receiver->d_func()->threadData->thread.loadAcquire() == mainThread() && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
&& sendThroughApplicationEventFilters(receiver, e)) { && sendThroughApplicationEventFilters(receiver, e)) {
filtered = true; filtered = true;
return filtered; return filtered;