Add a way for auxiliary threads to handle events without CoreApp
Long-lived threads started by Qt itself can now receive events even if QCoreApplication hasn't been created. This is required in all threads we start that will handle events, unless we're sure that the thread will exit before the global application object begins destruction. Otherwise, those threads will have race conditions dealing with the event delivery system trying to call the QCoreApplication::notify() virtual while the object is being destroyed. Change-Id: I27eaacb532114dd188c4ffff13d4ad2a4bb443e6 Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
This commit is contained in:
parent
9d98584a83
commit
10c529b08d
@ -538,6 +538,14 @@ QThread *QCoreApplicationPrivate::mainThread()
|
|||||||
return theMainThread;
|
return theMainThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QCoreApplicationPrivate::threadRequiresCoreApplication()
|
||||||
|
{
|
||||||
|
QThreadData *data = QThreadData::current(false);
|
||||||
|
if (!data)
|
||||||
|
return true; // default setting
|
||||||
|
return data->requiresCoreApplication;
|
||||||
|
}
|
||||||
|
|
||||||
void QCoreApplicationPrivate::checkReceiverThread(QObject *receiver)
|
void QCoreApplicationPrivate::checkReceiverThread(QObject *receiver)
|
||||||
{
|
{
|
||||||
QThread *currentThread = QThread::currentThread();
|
QThread *currentThread = QThread::currentThread();
|
||||||
@ -926,6 +934,8 @@ bool QCoreApplication::isQuitLockEnabled()
|
|||||||
return quitLockRefEnabled;
|
return quitLockRefEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool doNotify(QObject *, QEvent *);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Enables the ability of the QEventLoopLocker feature to quit
|
Enables the ability of the QEventLoopLocker feature to quit
|
||||||
the application.
|
the application.
|
||||||
@ -960,7 +970,8 @@ bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
|
|||||||
*/
|
*/
|
||||||
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
|
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
|
||||||
{
|
{
|
||||||
if (!self)
|
bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
|
||||||
|
if (!self && selfRequired)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Make it possible for Qt Script to hook into events even
|
// Make it possible for Qt Script to hook into events even
|
||||||
@ -978,6 +989,8 @@ bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
|
|||||||
QObjectPrivate *d = receiver->d_func();
|
QObjectPrivate *d = receiver->d_func();
|
||||||
QThreadData *threadData = d->threadData;
|
QThreadData *threadData = d->threadData;
|
||||||
QScopedLoopLevelCounter loopLevelCounter(threadData);
|
QScopedLoopLevelCounter loopLevelCounter(threadData);
|
||||||
|
if (!selfRequired)
|
||||||
|
return doNotify(receiver, event);
|
||||||
return self->notify(receiver, event);
|
return self->notify(receiver, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1039,7 +1052,11 @@ bool QCoreApplication::notify(QObject *receiver, QEvent *event)
|
|||||||
// no events are delivered after ~QCoreApplication() has started
|
// no events are delivered after ~QCoreApplication() has started
|
||||||
if (QCoreApplicationPrivate::is_app_closing)
|
if (QCoreApplicationPrivate::is_app_closing)
|
||||||
return true;
|
return true;
|
||||||
|
return doNotify(receiver, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool doNotify(QObject *receiver, QEvent *event)
|
||||||
|
{
|
||||||
if (receiver == 0) { // serious error
|
if (receiver == 0) { // serious error
|
||||||
qWarning("QCoreApplication::notify: Unexpected null receiver");
|
qWarning("QCoreApplication::notify: Unexpected null receiver");
|
||||||
return true;
|
return true;
|
||||||
@ -1054,7 +1071,10 @@ bool QCoreApplication::notify(QObject *receiver, QEvent *event)
|
|||||||
|
|
||||||
bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event)
|
bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event)
|
||||||
{
|
{
|
||||||
if (receiver->d_func()->threadData == this->threadData && extraData) {
|
// We can't access the application event filters outside of the main thread (race conditions)
|
||||||
|
Q_ASSERT(receiver->d_func()->threadData->thread == mainThread());
|
||||||
|
|
||||||
|
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
|
||||||
for (int i = 0; i < extraData->eventFilters.size(); ++i) {
|
for (int i = 0; i < extraData->eventFilters.size(); ++i) {
|
||||||
QObject *obj = extraData->eventFilters.at(i);
|
QObject *obj = extraData->eventFilters.at(i);
|
||||||
@ -1097,7 +1117,9 @@ bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, Q
|
|||||||
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
|
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->d_func()->sendThroughApplicationEventFilters(receiver, event))
|
if (QCoreApplication::self
|
||||||
|
&& receiver->d_func()->threadData->thread == mainThread()
|
||||||
|
&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event))
|
||||||
return true;
|
return true;
|
||||||
// send to all receiver event filters
|
// send to all receiver event filters
|
||||||
if (sendThroughObjectEventFilters(receiver, event))
|
if (sendThroughObjectEventFilters(receiver, event))
|
||||||
|
@ -107,6 +107,8 @@ public:
|
|||||||
|
|
||||||
static QThread *theMainThread;
|
static QThread *theMainThread;
|
||||||
static QThread *mainThread();
|
static QThread *mainThread();
|
||||||
|
static bool threadRequiresCoreApplication();
|
||||||
|
|
||||||
static void sendPostedEvents(QObject *receiver, int event_type, QThreadData *data);
|
static void sendPostedEvents(QObject *receiver, int event_type, QThreadData *data);
|
||||||
|
|
||||||
static void checkReceiverThread(QObject *receiver);
|
static void checkReceiverThread(QObject *receiver);
|
||||||
|
@ -93,7 +93,7 @@ QEventLoop::QEventLoop(QObject *parent)
|
|||||||
: QObject(*new QEventLoopPrivate, parent)
|
: QObject(*new QEventLoopPrivate, parent)
|
||||||
{
|
{
|
||||||
Q_D(QEventLoop);
|
Q_D(QEventLoop);
|
||||||
if (!QCoreApplication::instance()) {
|
if (!QCoreApplication::instance() && QCoreApplicationPrivate::threadRequiresCoreApplication()) {
|
||||||
qWarning("QEventLoop: Cannot be used without QApplication");
|
qWarning("QEventLoop: Cannot be used without QApplication");
|
||||||
} else if (!d->threadData->eventDispatcher.load()) {
|
} else if (!d->threadData->eventDispatcher.load()) {
|
||||||
QThreadPrivate::createEventDispatcher(d->threadData);
|
QThreadPrivate::createEventDispatcher(d->threadData);
|
||||||
|
@ -50,7 +50,8 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
QThreadData::QThreadData(int initialRefCount)
|
QThreadData::QThreadData(int initialRefCount)
|
||||||
: _ref(initialRefCount), loopLevel(0), thread(0), threadId(0),
|
: _ref(initialRefCount), loopLevel(0), thread(0), threadId(0),
|
||||||
eventDispatcher(0), quitNow(false), canWait(true), isAdopted(false)
|
eventDispatcher(0),
|
||||||
|
quitNow(false), canWait(true), isAdopted(false), requiresCoreApplication(true)
|
||||||
{
|
{
|
||||||
// fprintf(stderr, "QThreadData %p created\n", this);
|
// fprintf(stderr, "QThreadData %p created\n", this);
|
||||||
}
|
}
|
||||||
@ -867,4 +868,28 @@ bool QThread::isInterruptionRequested() const
|
|||||||
return d->interruptionRequested;
|
return d->interruptionRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class QDaemonThread
|
||||||
|
\since 5.5
|
||||||
|
\brief The QDaemonThread provides a class to manage threads that outlive QCoreApplication
|
||||||
|
\internal
|
||||||
|
|
||||||
|
Note: don't try to deliver events from the started() signal.
|
||||||
|
*/
|
||||||
|
static void setThreadDoesNotRequireCoreApplication()
|
||||||
|
{
|
||||||
|
QThreadData::current()->requiresCoreApplication = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDaemonThread::QDaemonThread(QObject *parent)
|
||||||
|
: QThread(parent)
|
||||||
|
{
|
||||||
|
// QThread::started() is emitted from the thread we start
|
||||||
|
connect(this, &QThread::started, setThreadDoesNotRequireCoreApplication);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDaemonThread::~QDaemonThread()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -135,6 +135,13 @@ private:
|
|||||||
|
|
||||||
#ifndef QT_NO_THREAD
|
#ifndef QT_NO_THREAD
|
||||||
|
|
||||||
|
class Q_CORE_EXPORT QDaemonThread : public QThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QDaemonThread(QObject *parent = 0);
|
||||||
|
~QDaemonThread();
|
||||||
|
};
|
||||||
|
|
||||||
class QThreadPrivate : public QObjectPrivate
|
class QThreadPrivate : public QObjectPrivate
|
||||||
{
|
{
|
||||||
Q_DECLARE_PUBLIC(QThread)
|
Q_DECLARE_PUBLIC(QThread)
|
||||||
@ -224,7 +231,7 @@ public:
|
|||||||
QThreadData(int initialRefCount = 1);
|
QThreadData(int initialRefCount = 1);
|
||||||
~QThreadData();
|
~QThreadData();
|
||||||
|
|
||||||
static QThreadData *current(bool createIfNecessary = true);
|
static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
|
||||||
static void clearCurrentThreadData();
|
static void clearCurrentThreadData();
|
||||||
static QThreadData *get2(QThread *thread)
|
static QThreadData *get2(QThread *thread)
|
||||||
{ Q_ASSERT_X(thread != 0, "QThread", "internal error"); return thread->d_func()->data; }
|
{ Q_ASSERT_X(thread != 0, "QThread", "internal error"); return thread->d_func()->data; }
|
||||||
@ -278,6 +285,7 @@ public:
|
|||||||
bool quitNow;
|
bool quitNow;
|
||||||
bool canWait;
|
bool canWait;
|
||||||
bool isAdopted;
|
bool isAdopted;
|
||||||
|
bool requiresCoreApplication;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QScopedLoopLevelCounter
|
class QScopedLoopLevelCounter
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
#include "qthread.h"
|
#include "qthread.h"
|
||||||
#include "QtCore/qcoreapplication.h"
|
#include "QtCore/qcoreapplication.h"
|
||||||
|
|
||||||
|
#include <QtCore/private/qthread_p.h>
|
||||||
|
|
||||||
#include "qnetworkcookiejar.h"
|
#include "qnetworkcookiejar.h"
|
||||||
|
|
||||||
#ifndef QT_NO_HTTP
|
#ifndef QT_NO_HTTP
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include <QtCore/qstringlist.h>
|
#include <QtCore/qstringlist.h>
|
||||||
#include <QtCore/qthread.h>
|
#include <QtCore/qthread.h>
|
||||||
#include <QtCore/private/qcoreapplication_p.h>
|
#include <QtCore/private/qcoreapplication_p.h>
|
||||||
|
#include <QtCore/private/qthread_p.h>
|
||||||
|
|
||||||
#ifndef QT_NO_BEARERMANAGEMENT
|
#ifndef QT_NO_BEARERMANAGEMENT
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ QNetworkConfigurationManagerPrivate::QNetworkConfigurationManagerPrivate()
|
|||||||
void QNetworkConfigurationManagerPrivate::initialize()
|
void QNetworkConfigurationManagerPrivate::initialize()
|
||||||
{
|
{
|
||||||
//Two stage construction, because we only want to do this heavyweight work for the winner of the Q_GLOBAL_STATIC race.
|
//Two stage construction, because we only want to do this heavyweight work for the winner of the Q_GLOBAL_STATIC race.
|
||||||
bearerThread = new QThread();
|
bearerThread = new QDaemonThread();
|
||||||
bearerThread->setObjectName(QStringLiteral("Qt bearer thread"));
|
bearerThread->setObjectName(QStringLiteral("Qt bearer thread"));
|
||||||
|
|
||||||
bearerThread->moveToThread(QCoreApplicationPrivate::mainThread()); // because cleanup() is called in main thread context.
|
bearerThread->moveToThread(QCoreApplicationPrivate::mainThread()); // because cleanup() is called in main thread context.
|
||||||
|
@ -292,7 +292,7 @@ void QEvdevTabletHandler::readData()
|
|||||||
|
|
||||||
|
|
||||||
QEvdevTabletHandlerThread::QEvdevTabletHandlerThread(const QString &spec, QObject *parent)
|
QEvdevTabletHandlerThread::QEvdevTabletHandlerThread(const QString &spec, QObject *parent)
|
||||||
: QThread(parent), m_spec(spec), m_handler(0)
|
: QDaemonThread(parent), m_spec(spec), m_handler(0)
|
||||||
{
|
{
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QtCore/private/qthread_p.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ private:
|
|||||||
QEvdevTabletData *d;
|
QEvdevTabletData *d;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QEvdevTabletHandlerThread : public QThread
|
class QEvdevTabletHandlerThread : public QDaemonThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit QEvdevTabletHandlerThread(const QString &spec, QObject *parent = 0);
|
explicit QEvdevTabletHandlerThread(const QString &spec, QObject *parent = 0);
|
||||||
|
@ -642,7 +642,7 @@ void QEvdevTouchScreenData::reportPoints()
|
|||||||
|
|
||||||
|
|
||||||
QEvdevTouchScreenHandlerThread::QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent)
|
QEvdevTouchScreenHandlerThread::QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent)
|
||||||
: QThread(parent), m_device(device), m_spec(spec), m_handler(0)
|
: QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(0)
|
||||||
{
|
{
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QtCore/private/qthread_p.h>
|
||||||
#include <qpa/qwindowsysteminterface.h>
|
#include <qpa/qwindowsysteminterface.h>
|
||||||
|
|
||||||
#if !defined(QT_NO_MTDEV)
|
#if !defined(QT_NO_MTDEV)
|
||||||
@ -80,7 +81,7 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class QEvdevTouchScreenHandlerThread : public QThread
|
class QEvdevTouchScreenHandlerThread : public QDaemonThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent = 0);
|
explicit QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent = 0);
|
||||||
|
@ -3688,7 +3688,9 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
|
|||||||
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
|
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
|
||||||
{
|
{
|
||||||
// send to all application event filters
|
// send to all application event filters
|
||||||
if (sendThroughApplicationEventFilters(receiver, e))
|
if (threadRequiresCoreApplication()
|
||||||
|
&& receiver->d_func()->threadData->thread == mainThread()
|
||||||
|
&& sendThroughApplicationEventFilters(receiver, e))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (receiver->isWidgetType()) {
|
if (receiver->isWidgetType()) {
|
||||||
|
@ -3,3 +3,4 @@ TARGET = tst_qcoreapplication
|
|||||||
QT = core testlib core-private
|
QT = core testlib core-private
|
||||||
SOURCES = tst_qcoreapplication.cpp
|
SOURCES = tst_qcoreapplication.cpp
|
||||||
HEADERS = tst_qcoreapplication.h
|
HEADERS = tst_qcoreapplication.h
|
||||||
|
requires(contains(QT_CONFIG,private_tests))
|
||||||
|
@ -78,6 +78,21 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Thread : public QDaemonThread
|
||||||
|
{
|
||||||
|
void run() Q_DECL_OVERRIDE
|
||||||
|
{
|
||||||
|
QThreadData *data = QThreadData::current();
|
||||||
|
QVERIFY(!data->requiresCoreApplication); // daemon thread
|
||||||
|
data->requiresCoreApplication = requiresCoreApplication;
|
||||||
|
QThread::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Thread() : requiresCoreApplication(true) {}
|
||||||
|
bool requiresCoreApplication;
|
||||||
|
};
|
||||||
|
|
||||||
void tst_QCoreApplication::sendEventsOnProcessEvents()
|
void tst_QCoreApplication::sendEventsOnProcessEvents()
|
||||||
{
|
{
|
||||||
int argc = 1;
|
int argc = 1;
|
||||||
@ -853,6 +868,41 @@ void tst_QCoreApplication::applicationEventFilters_auxThread()
|
|||||||
QVERIFY(!spy.recordedEvents.contains(QEvent::User + 1));
|
QVERIFY(!spy.recordedEvents.contains(QEvent::User + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QCoreApplication::threadedEventDelivery_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<bool>("requiresCoreApplication");
|
||||||
|
QTest::addColumn<bool>("createCoreApplication");
|
||||||
|
QTest::addColumn<bool>("eventsReceived");
|
||||||
|
|
||||||
|
// invalid combination:
|
||||||
|
//QTest::newRow("default-without-coreapp") << true << false << false;
|
||||||
|
QTest::newRow("default") << true << true << true;
|
||||||
|
QTest::newRow("independent-without-coreapp") << false << false << true;
|
||||||
|
QTest::newRow("independent-with-coreapp") << false << true << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// posts the event before the QCoreApplication is destroyed, starts thread after
|
||||||
|
void tst_QCoreApplication::threadedEventDelivery()
|
||||||
|
{
|
||||||
|
QFETCH(bool, requiresCoreApplication);
|
||||||
|
QFETCH(bool, createCoreApplication);
|
||||||
|
QFETCH(bool, eventsReceived);
|
||||||
|
|
||||||
|
int argc = 1;
|
||||||
|
char *argv[] = { const_cast<char*>(QTest::currentAppName()) };
|
||||||
|
QScopedPointer<TestApplication> app(createCoreApplication ? new TestApplication(argc, argv) : 0);
|
||||||
|
|
||||||
|
Thread thread;
|
||||||
|
thread.requiresCoreApplication = requiresCoreApplication;
|
||||||
|
ThreadedEventReceiver receiver;
|
||||||
|
receiver.moveToThread(&thread);
|
||||||
|
QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User + 1)));
|
||||||
|
|
||||||
|
thread.start();
|
||||||
|
QVERIFY(thread.wait(1000));
|
||||||
|
QCOMPARE(receiver.recordedEvents.contains(QEvent::User + 1), eventsReceived);
|
||||||
|
}
|
||||||
|
|
||||||
static void createQObjectOnDestruction()
|
static void createQObjectOnDestruction()
|
||||||
{
|
{
|
||||||
// Make sure that we can create a QObject after the last QObject has been
|
// Make sure that we can create a QObject after the last QObject has been
|
||||||
|
@ -61,6 +61,8 @@ private slots:
|
|||||||
void QTBUG31606_QEventDestructorDeadLock();
|
void QTBUG31606_QEventDestructorDeadLock();
|
||||||
void applicationEventFilters_mainThread();
|
void applicationEventFilters_mainThread();
|
||||||
void applicationEventFilters_auxThread();
|
void applicationEventFilters_auxThread();
|
||||||
|
void threadedEventDelivery_data();
|
||||||
|
void threadedEventDelivery();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TST_QCOREAPPLICATION_H
|
#endif // TST_QCOREAPPLICATION_H
|
||||||
|
Loading…
Reference in New Issue
Block a user