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:
Thiago Macieira 2015-04-16 17:21:37 -07:00
parent 9d98584a83
commit 10c529b08d
15 changed files with 129 additions and 12 deletions

View File

@ -538,6 +538,14 @@ QThread *QCoreApplicationPrivate::mainThread()
return theMainThread;
}
bool QCoreApplicationPrivate::threadRequiresCoreApplication()
{
QThreadData *data = QThreadData::current(false);
if (!data)
return true; // default setting
return data->requiresCoreApplication;
}
void QCoreApplicationPrivate::checkReceiverThread(QObject *receiver)
{
QThread *currentThread = QThread::currentThread();
@ -926,6 +934,8 @@ bool QCoreApplication::isQuitLockEnabled()
return quitLockRefEnabled;
}
static bool doNotify(QObject *, QEvent *);
/*!
Enables the ability of the QEventLoopLocker feature to quit
the application.
@ -960,7 +970,8 @@ bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
*/
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
if (!self)
bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
if (!self && selfRequired)
return false;
// 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();
QThreadData *threadData = d->threadData;
QScopedLoopLevelCounter loopLevelCounter(threadData);
if (!selfRequired)
return doNotify(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
if (QCoreApplicationPrivate::is_app_closing)
return true;
return doNotify(receiver, event);
}
static bool doNotify(QObject *receiver, QEvent *event)
{
if (receiver == 0) { // serious error
qWarning("QCoreApplication::notify: Unexpected null receiver");
return true;
@ -1054,7 +1071,10 @@ bool QCoreApplication::notify(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
for (int i = 0; i < extraData->eventFilters.size(); ++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)
{
// 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;
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, event))

View File

@ -107,6 +107,8 @@ public:
static QThread *theMainThread;
static QThread *mainThread();
static bool threadRequiresCoreApplication();
static void sendPostedEvents(QObject *receiver, int event_type, QThreadData *data);
static void checkReceiverThread(QObject *receiver);

View File

@ -93,7 +93,7 @@ QEventLoop::QEventLoop(QObject *parent)
: QObject(*new QEventLoopPrivate, parent)
{
Q_D(QEventLoop);
if (!QCoreApplication::instance()) {
if (!QCoreApplication::instance() && QCoreApplicationPrivate::threadRequiresCoreApplication()) {
qWarning("QEventLoop: Cannot be used without QApplication");
} else if (!d->threadData->eventDispatcher.load()) {
QThreadPrivate::createEventDispatcher(d->threadData);

View File

@ -50,7 +50,8 @@ QT_BEGIN_NAMESPACE
QThreadData::QThreadData(int initialRefCount)
: _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);
}
@ -867,4 +868,28 @@ bool QThread::isInterruptionRequested() const
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

View File

@ -135,6 +135,13 @@ private:
#ifndef QT_NO_THREAD
class Q_CORE_EXPORT QDaemonThread : public QThread
{
public:
QDaemonThread(QObject *parent = 0);
~QDaemonThread();
};
class QThreadPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QThread)
@ -224,7 +231,7 @@ public:
QThreadData(int initialRefCount = 1);
~QThreadData();
static QThreadData *current(bool createIfNecessary = true);
static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
static void clearCurrentThreadData();
static QThreadData *get2(QThread *thread)
{ Q_ASSERT_X(thread != 0, "QThread", "internal error"); return thread->d_func()->data; }
@ -278,6 +285,7 @@ public:
bool quitNow;
bool canWait;
bool isAdopted;
bool requiresCoreApplication;
};
class QScopedLoopLevelCounter

View File

@ -49,6 +49,8 @@
#include "qthread.h"
#include "QtCore/qcoreapplication.h"
#include <QtCore/private/qthread_p.h>
#include "qnetworkcookiejar.h"
#ifndef QT_NO_HTTP

View File

@ -41,6 +41,7 @@
#include <QtCore/qstringlist.h>
#include <QtCore/qthread.h>
#include <QtCore/private/qcoreapplication_p.h>
#include <QtCore/private/qthread_p.h>
#ifndef QT_NO_BEARERMANAGEMENT
@ -61,7 +62,7 @@ QNetworkConfigurationManagerPrivate::QNetworkConfigurationManagerPrivate()
void QNetworkConfigurationManagerPrivate::initialize()
{
//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->moveToThread(QCoreApplicationPrivate::mainThread()); // because cleanup() is called in main thread context.

View File

@ -292,7 +292,7 @@ void QEvdevTabletHandler::readData()
QEvdevTabletHandlerThread::QEvdevTabletHandlerThread(const QString &spec, QObject *parent)
: QThread(parent), m_spec(spec), m_handler(0)
: QDaemonThread(parent), m_spec(spec), m_handler(0)
{
start();
}

View File

@ -48,6 +48,7 @@
#include <QObject>
#include <QString>
#include <QThread>
#include <QtCore/private/qthread_p.h>
QT_BEGIN_NAMESPACE
@ -68,7 +69,7 @@ private:
QEvdevTabletData *d;
};
class QEvdevTabletHandlerThread : public QThread
class QEvdevTabletHandlerThread : public QDaemonThread
{
public:
explicit QEvdevTabletHandlerThread(const QString &spec, QObject *parent = 0);

View File

@ -642,7 +642,7 @@ void QEvdevTouchScreenData::reportPoints()
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();
}

View File

@ -49,6 +49,7 @@
#include <QString>
#include <QList>
#include <QThread>
#include <QtCore/private/qthread_p.h>
#include <qpa/qwindowsysteminterface.h>
#if !defined(QT_NO_MTDEV)
@ -80,7 +81,7 @@ private:
#endif
};
class QEvdevTouchScreenHandlerThread : public QThread
class QEvdevTouchScreenHandlerThread : public QDaemonThread
{
public:
explicit QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent = 0);

View File

@ -3688,7 +3688,9 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
// send to all application event filters
if (sendThroughApplicationEventFilters(receiver, e))
if (threadRequiresCoreApplication()
&& receiver->d_func()->threadData->thread == mainThread()
&& sendThroughApplicationEventFilters(receiver, e))
return true;
if (receiver->isWidgetType()) {

View File

@ -3,3 +3,4 @@ TARGET = tst_qcoreapplication
QT = core testlib core-private
SOURCES = tst_qcoreapplication.cpp
HEADERS = tst_qcoreapplication.h
requires(contains(QT_CONFIG,private_tests))

View File

@ -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()
{
int argc = 1;
@ -853,6 +868,41 @@ void tst_QCoreApplication::applicationEventFilters_auxThread()
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()
{
// Make sure that we can create a QObject after the last QObject has been

View File

@ -61,6 +61,8 @@ private slots:
void QTBUG31606_QEventDestructorDeadLock();
void applicationEventFilters_mainThread();
void applicationEventFilters_auxThread();
void threadedEventDelivery_data();
void threadedEventDelivery();
};
#endif // TST_QCOREAPPLICATION_H