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;
// create the socket notifiers
if (threadData->hasEventDispatcher()) {
if (threadData.loadRelaxed()->hasEventDispatcher()) {
if (&channel == &stdinChannel) {
channel.notifier = new QSocketNotifier(channel.pipe[1],
QSocketNotifier::Write, q);
@ -377,7 +377,7 @@ void QProcessPrivate::startProcess()
return;
}
if (threadData->hasEventDispatcher()) {
if (threadData.loadRelaxed()->hasEventDispatcher()) {
startupSocketNotifier = new QSocketNotifier(childStartedPipe[0],
QSocketNotifier::Read, q);
QObject::connect(startupSocketNotifier, SIGNAL(activated(int)),
@ -517,7 +517,7 @@ void QProcessPrivate::startProcess()
if (stderrChannel.pipe[0] != -1)
::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);
QObject::connect(deathNotifier, SIGNAL(activated(int)),
q, SLOT(_q_processDied()));

View File

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

View File

@ -135,23 +135,6 @@
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)
extern QString qAppFileName();
#endif
@ -517,25 +500,27 @@ QCoreApplicationPrivate::~QCoreApplicationPrivate()
void QCoreApplicationPrivate::cleanupThreadData()
{
if (threadData && !threadData_clean) {
auto thisThreadData = threadData.loadRelaxed();
if (thisThreadData && !threadData_clean) {
#if QT_CONFIG(thread)
void *data = &threadData->tls;
void *data = &thisThreadData->tls;
QThreadStorageData::finish((void **)data);
#endif
// 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);
for (int i = 0; i < threadData->postEventList.size(); ++i) {
const QPostEvent &pe = threadData->postEventList.at(i);
const auto locker = qt_scoped_lock(thisThreadData->postEventList.mutex);
for (int i = 0; i < thisThreadData->postEventList.size(); ++i) {
const QPostEvent &pe = thisThreadData->postEventList.at(i);
if (pe.event) {
--pe.receiver->d_func()->postedEvents;
pe.event->posted = false;
delete pe.event;
}
}
threadData->postEventList.clear();
threadData->postEventList.recursion = 0;
threadData->quitNow = false;
thisThreadData->postEventList.clear();
thisThreadData->postEventList.recursion = 0;
thisThreadData->quitNow = false;
threadData_clean = true;
}
}
@ -858,7 +843,8 @@ void QCoreApplicationPrivate::init()
#ifndef QT_NO_QOBJECT
// use the event dispatcher created by the app programmer (if any)
Q_ASSERT(!eventDispatcher);
eventDispatcher = threadData->eventDispatcher.loadRelaxed();
auto thisThreadData = threadData.loadRelaxed();
eventDispatcher = thisThreadData->eventDispatcher.loadRelaxed();
// otherwise we create one
if (!eventDispatcher)
@ -866,11 +852,11 @@ void QCoreApplicationPrivate::init()
Q_ASSERT(eventDispatcher);
if (!eventDispatcher->parent()) {
eventDispatcher->moveToThread(threadData->thread.loadAcquire());
eventDispatcher->moveToThread(thisThreadData->thread.loadAcquire());
eventDispatcher->setParent(q);
}
threadData->eventDispatcher = eventDispatcher;
thisThreadData->eventDispatcher = eventDispatcher;
eventDispatcherReady();
#endif
@ -914,7 +900,7 @@ QCoreApplication::~QCoreApplication()
#endif
#ifndef QT_NO_QOBJECT
d_func()->threadData->eventDispatcher = nullptr;
d_func()->threadData.loadRelaxed()->eventDispatcher = nullptr;
if (QCoreApplicationPrivate::eventDispatcher)
QCoreApplicationPrivate::eventDispatcher->closingDown();
QCoreApplicationPrivate::eventDispatcher = nullptr;
@ -1185,7 +1171,7 @@ static bool doNotify(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)
Q_ASSERT(receiver->d_func()->threadData->thread.loadAcquire() == mainThread());
Q_ASSERT(receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread());
if (extraData) {
// 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)
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)) {
filtered = true;
return filtered;
@ -1414,7 +1400,7 @@ int QCoreApplication::exec()
void QCoreApplicationPrivate::execCleanup()
{
threadData->quitNow = false;
threadData.loadRelaxed()->quitNow = false;
in_exec = false;
if (!aboutToQuitEmitted)
emit q_func()->aboutToQuit(QCoreApplication::QPrivateSignal());
@ -1451,7 +1437,7 @@ void QCoreApplication::exit(int returnCode)
{
if (!self)
return;
QThreadData *data = self->d_func()->threadData;
QThreadData *data = self->d_func()->threadData.loadRelaxed();
data->quitNow = true;
for (int i = 0; i < data->eventLoops.size(); ++i) {
QEventLoop *eventLoop = data->eventLoops.at(i);
@ -1501,6 +1487,38 @@ bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
#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
@ -1536,32 +1554,14 @@ void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
return;
}
QThreadData * volatile * pdata = &receiver->d_func()->threadData;
QThreadData *data = *pdata;
if (!data) {
auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
if (!locker.threadData) {
// posting during destruction? just delete the event to prevent a leak
delete event;
return;
}
// lock the post event mutex
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);
QThreadData *data = locker.threadData;
// if this is one of the compressible events, do compression
if (receiver->d_func()->postedEvents
@ -1860,8 +1860,8 @@ void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type
void QCoreApplication::removePostedEvents(QObject *receiver, int eventType)
{
QThreadData *data = receiver ? receiver->d_func()->threadData : QThreadData::current();
auto locker = qt_unique_lock(data->postEventList.mutex);
auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
QThreadData *data = locker.threadData;
// the QObject destructor calls this function directly. this can
// happen while the event loop is in the middle of posting events,

View File

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

View File

@ -918,7 +918,7 @@ QDebug operator<<(QDebug dbg, const MSG &msg)
#ifndef QT_NO_QOBJECT
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);
if (data->postEventList.size() == 0)

View File

@ -463,13 +463,15 @@ bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
// we are awake, broadcast it
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_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0;
const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents;
const bool canWait = (d->threadData->canWaitLocked()
const bool canWait = (threadData->canWaitLocked()
&& !d->interrupt.loadRelaxed()
&& wait_for_events);

View File

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

View File

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

View File

@ -212,11 +212,12 @@ QObjectPrivate::QObjectPrivate(int version)
QObjectPrivate::~QObjectPrivate()
{
auto thisThreadData = threadData.loadRelaxed();
if (extraData && !extraData->runningTimers.isEmpty()) {
if (Q_LIKELY(threadData->thread.loadAcquire() == QThread::currentThread())) {
if (Q_LIKELY(thisThreadData->thread.loadAcquire() == QThread::currentThread())) {
// unregister pending timers
if (threadData->hasEventDispatcher())
threadData->eventDispatcher.loadRelaxed()->unregisterTimers(q_ptr);
if (thisThreadData->hasEventDispatcher())
thisThreadData->eventDispatcher.loadRelaxed()->unregisterTimers(q_ptr);
// release the timer ids back to the pool
for (int i = 0; i < extraData->runningTimers.size(); ++i)
@ -229,7 +230,7 @@ QObjectPrivate::~QObjectPrivate()
if (postedEvents)
QCoreApplication::removePostedEvents(q_ptr, 0);
threadData->deref();
thisThreadData->deref();
if (metaObject) metaObject->objectDestroyed(q_ptr);
@ -920,11 +921,12 @@ QObject::QObject(QObjectPrivate &dd, QObject *parent)
Q_D(QObject);
d_ptr->q_ptr = this;
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
auto threadData = (parent && !parent->thread()) ? parent->d_func()->threadData.loadRelaxed() : QThreadData::current();
threadData->ref();
d->threadData.storeRelaxed(threadData);
if (parent) {
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;
if (d->isWidget) {
if (parent) {
@ -936,7 +938,7 @@ QObject::QObject(QObjectPrivate &dd, QObject *parent)
setParent(parent);
}
} QT_CATCH(...) {
d->threadData->deref();
threadData->deref();
QT_RETHROW;
}
}
@ -1320,7 +1322,7 @@ bool QObject::event(QEvent *e)
case QEvent::ThreadChange: {
Q_D(QObject);
QThreadData *threadData = d->threadData;
QThreadData *threadData = d->threadData.loadRelaxed();
QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.loadRelaxed();
if (eventDispatcher) {
QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
@ -1487,7 +1489,7 @@ bool QObject::blockSignals(bool block) noexcept
*/
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);
if (d->threadData->thread.loadAcquire() == targetThread) {
if (d->threadData.loadRelaxed()->thread.loadAcquire() == targetThread) {
// object is already in this thread
return;
}
@ -1550,13 +1552,14 @@ void QObject::moveToThread(QThread *targetThread)
QThreadData *currentData = QThreadData::current();
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
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"
"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
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
targetData->ref();
threadData->deref();
threadData = targetData;
threadData.loadRelaxed()->deref();
// synchronizes with loadAcquire e.g. in QCoreApplication::postEvent
threadData.storeRelease(targetData);
for (int i = 0; i < children.size(); ++i) {
QObject *child = children.at(i);
@ -1666,7 +1671,7 @@ void QObjectPrivate::_q_reregisterTimers(void *pointer)
{
Q_Q(QObject);
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) {
const QAbstractEventDispatcher::TimerInfo &ti = timerList->at(i);
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");
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");
return 0;
}
@ -1732,7 +1739,7 @@ int QObject::startTimer(int interval, Qt::TimerType timerType)
qWarning("QObject::startTimer: Timers cannot be started from another thread");
return 0;
}
int timerId = d->threadData->eventDispatcher.loadRelaxed()->registerTimer(interval, timerType, this);
int timerId = thisThreadData->eventDispatcher.loadRelaxed()->registerTimer(interval, timerType, this);
if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData;
d->extraData->runningTimers.append(timerId);
@ -1806,8 +1813,9 @@ void QObject::killTimer(int id)
return;
}
if (d->threadData->hasEventDispatcher())
d->threadData->eventDispatcher.loadRelaxed()->unregisterTimer(id);
auto thisThreadData = d->threadData.loadRelaxed();
if (thisThreadData->hasEventDispatcher())
thisThreadData->eventDispatcher.loadRelaxed()->unregisterTimer(id);
d->extraData->runningTimers.remove(at);
QAbstractEventDispatcherPrivate::releaseTimerId(id);
@ -3774,7 +3782,7 @@ void doActivate(QObject *sender, int signal_index, void **argv)
list = &signalVector->at(-1);
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
// during the signal emission are not emitted in this emission.

View File

@ -374,8 +374,13 @@ public:
}
public:
ExtraData *extraData; // extra data set by the user
QThreadData *getThreadData() const { return threadData; }
QThreadData *threadData; // id of the thread that owns the object
QThreadData *getThreadData() const { return threadData.loadAcquire(); }
// 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>;
QAtomicPointer<ConnectionData> connections;

View File

@ -147,12 +147,14 @@ QSocketNotifier::QSocketNotifier(qintptr socket, Type type, QObject *parent)
d->sntype = type;
d->snenabled = true;
auto thisThreadData = d->threadData.loadRelaxed();
if (socket < 0)
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");
else
d->threadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
thisThreadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
}
/*!
@ -234,16 +236,19 @@ void QSocketNotifier::setEnabled(bool enable)
return;
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;
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
return;
}
if (d->snenabled)
d->threadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
thisThreadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
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)
{
Q_D(QWinEventNotifier);
QAbstractEventDispatcher *eventDispatcher = d->threadData->eventDispatcher.loadRelaxed();
QAbstractEventDispatcher *eventDispatcher = d->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
if (Q_UNLIKELY(!eventDispatcher)) {
qWarning("QWinEventNotifier: Can only be used with threads started with QThread");
return;
@ -197,7 +197,7 @@ void QWinEventNotifier::setEnabled(bool enable)
return;
d->enabled = enable;
QAbstractEventDispatcher *eventDispatcher = d->threadData->eventDispatcher.loadRelaxed();
QAbstractEventDispatcher *eventDispatcher = d->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
if (!eventDispatcher) { // perhaps application is shutting down
if (!enable && d->waitHandle != nullptr)
d->unregisterWaitObject();
@ -256,7 +256,7 @@ void QWinEventNotifierPrivate::unregisterWaitObject()
static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/)
{
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.
// https://bugreports.qt.io/browse/QTBUG-70214

View File

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

View File

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

View File

@ -1341,7 +1341,7 @@ void QNativeSocketEngine::setReadNotificationEnabled(bool enable)
Q_D(QNativeSocketEngine);
if (d->readNotifier) {
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->setEnabled(true);
}
@ -1358,7 +1358,7 @@ void QNativeSocketEngine::setWriteNotificationEnabled(bool enable)
Q_D(QNativeSocketEngine);
if (d->writeNotifier) {
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->setEnabled(true);
}
@ -1375,7 +1375,7 @@ void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable)
Q_D(QNativeSocketEngine);
if (d->exceptNotifier) {
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->setEnabled(true);
}

View File

@ -149,7 +149,7 @@ QT_USE_NAMESPACE
if ([reflectionDelegate respondsToSelector:_cmd])
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,
// or as a part of a native Cocoa application. In any case it should be fine to
// terminate now.
@ -359,7 +359,7 @@ QT_USE_NAMESPACE
if (!platformItem || platformItem->menu())
return;
QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData);
QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed());
QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]];
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);

View File

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

View File

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