summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/thread/qthread/tst_qthread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/thread/qthread/tst_qthread.cpp')
-rw-r--r--tests/auto/corelib/thread/qthread/tst_qthread.cpp450
1 files changed, 366 insertions, 84 deletions
diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
index 1e8380fbba..b163235d81 100644
--- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp
+++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
@@ -1,32 +1,14 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtTest/QtTest>
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QTest>
+#include <QTestEventLoop>
+#include <QSignalSpy>
+#include <QSemaphore>
+#include <QAbstractEventDispatcher>
+#if defined(Q_OS_WIN32)
+#include <QWinEventNotifier>
+#endif
#include <qcoreapplication.h>
#include <qelapsedtimer.h>
@@ -36,12 +18,15 @@
#include <qwaitcondition.h>
#include <qdebug.h>
#include <qmetaobject.h>
+#include <qscopeguard.h>
+#include <private/qobject_p.h>
+#include <private/qthread_p.h>
#ifdef Q_OS_UNIX
#include <pthread.h>
#endif
#if defined(Q_OS_WIN)
-#include <windows.h>
+#include <qt_windows.h>
#if defined(Q_OS_WIN32)
#include <process.h>
#endif
@@ -51,7 +36,9 @@
#include <exception>
#endif
-#include "emulationdetector.h"
+#include <QtTest/private/qemulationdetector_p.h>
+
+using namespace std::chrono_literals;
class tst_QThread : public QObject
{
@@ -66,6 +53,7 @@ private slots:
void setStackSize();
void exit();
void start();
+ void startSlotUsedInStringBasedLookups();
void terminate();
void quit();
void started();
@@ -85,6 +73,7 @@ private slots:
void adoptedThreadExecFinished();
void adoptMultipleThreads();
void adoptMultipleThreadsOverlap();
+ void adoptedThreadBindingStatus();
void exitAndStart();
void exitAndExec();
@@ -106,6 +95,13 @@ private slots:
void quitLock();
void create();
+ void createDestruction();
+ void threadIdReuse();
+
+ void terminateAndPrematureDestruction();
+ void terminateAndDoubleDestruction();
+
+ void bindingListCleanupAfterDelete();
};
enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute };
@@ -141,11 +137,13 @@ class Current_Thread : public QThread
public:
Qt::HANDLE id;
QThread *thread;
+ bool runCalledInCurrentThread = false;
void run() override
{
id = QThread::currentThreadId();
thread = QThread::currentThread();
+ runCalledInCurrentThread = thread->isCurrentThread();
}
};
@@ -241,9 +239,9 @@ public:
enum SleepType { Second, Millisecond, Microsecond };
SleepType sleepType;
- int interval;
+ ulong interval;
- int elapsed; // result, in *MILLISECONDS*
+ qint64 elapsed; // result, in *MILLISECONDS*
void run() override
{
@@ -252,17 +250,19 @@ public:
elapsed = 0;
QElapsedTimer timer;
timer.start();
+ std::chrono::nanoseconds dur{0};
switch (sleepType) {
case Second:
- sleep(interval);
+ dur = std::chrono::seconds{interval};
break;
case Millisecond:
- msleep(interval);
+ dur = std::chrono::milliseconds{interval};
break;
case Microsecond:
- usleep(interval);
+ dur = std::chrono::microseconds{interval};
break;
}
+ sleep(dur);
elapsed = timer.elapsed();
cond.wakeOne();
@@ -272,25 +272,30 @@ public:
void tst_QThread::currentThreadId()
{
Current_Thread thread;
- thread.id = 0;
- thread.thread = 0;
+ thread.id = nullptr;
+ thread.thread = nullptr;
thread.start();
QVERIFY(thread.wait(five_minutes));
- QVERIFY(thread.id != 0);
+ QVERIFY(thread.id != nullptr);
QVERIFY(thread.id != QThread::currentThreadId());
+ QVERIFY(!thread.isCurrentThread());
+ QVERIFY(!thread.thread->isCurrentThread());
+ QVERIFY(thread.QThread::thread()->isCurrentThread());
+ QVERIFY(thread.runCalledInCurrentThread);
+ QVERIFY(qApp->thread()->isCurrentThread());
}
void tst_QThread::currentThread()
{
- QVERIFY(QThread::currentThread() != 0);
+ QVERIFY(QThread::currentThread() != nullptr);
QCOMPARE(QThread::currentThread(), thread());
Current_Thread thread;
- thread.id = 0;
- thread.thread = 0;
+ thread.id = nullptr;
+ thread.thread = nullptr;
thread.start();
QVERIFY(thread.wait(five_minutes));
- QCOMPARE(thread.thread, (QThread *)&thread);
+ QCOMPARE(thread.thread, static_cast<QThread *>(&thread));
}
void tst_QThread::idealThreadCount()
@@ -469,11 +474,65 @@ void tst_QThread::start()
}
}
+class QThreadStarter : public QObject
+{
+ Q_OBJECT
+public:
+ using QObject::QObject;
+Q_SIGNALS:
+ void start(QThread::Priority);
+};
+
+class QThreadSelfStarter : public QThread
+{
+ Q_OBJECT
+public:
+ using QThread::QThread;
+
+ void check()
+ {
+ QVERIFY(connect(this, SIGNAL(starting(Priority)),
+ this, SLOT(start(Priority))));
+ QVERIFY(QMetaObject::invokeMethod(this, "start", Q_ARG(Priority, IdlePriority)));
+ }
+
+Q_SIGNALS:
+ void starting(Priority);
+};
+
+void tst_QThread::startSlotUsedInStringBasedLookups()
+{
+ // QTBUG-124723
+
+ QThread thread;
+ {
+ QThreadStarter starter;
+ QVERIFY(QObject::connect(&starter, SIGNAL(start(QThread::Priority)),
+ &thread, SLOT(start(QThread::Priority))));
+ }
+ {
+ QThreadSelfStarter selfStarter;
+ selfStarter.check();
+ if (QTest::currentTestFailed())
+ return;
+ selfStarter.exit();
+ selfStarter.wait(30s);
+ }
+ QVERIFY(QMetaObject::invokeMethod(&thread, "start",
+ Q_ARG(QThread::Priority, QThread::IdlePriority)));
+ thread.exit();
+ thread.wait(30s);
+}
+
void tst_QThread::terminate()
{
#if defined(Q_OS_ANDROID)
QSKIP("Thread termination is not supported on Android.");
#endif
+#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
+ QSKIP("Thread termination might result in stack underflow address sanitizer errors.");
+#endif
+
Terminate_Thread thread;
{
QMutexLocker locker(&thread.mutex);
@@ -540,6 +599,10 @@ void tst_QThread::terminated()
#if defined(Q_OS_ANDROID)
QSKIP("Thread termination is not supported on Android.");
#endif
+#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
+ QSKIP("Thread termination might result in stack underflow address sanitizer errors.");
+#endif
+
SignalRecorder recorder;
Terminate_Thread thread;
connect(&thread, SIGNAL(finished()), &recorder, SLOT(slot()), Qt::DirectConnection);
@@ -646,7 +709,7 @@ void noop(void*) { }
class NativeThreadWrapper
{
public:
- NativeThreadWrapper() : qthread(0), waitForStop(false) {}
+ NativeThreadWrapper() : qthread(nullptr), waitForStop(false) {}
void start(FunctionPointer functionPointer = noop, void *data = nullptr);
void startAndWait(FunctionPointer functionPointer = noop, void *data = nullptr);
void join();
@@ -672,8 +735,8 @@ void NativeThreadWrapper::start(FunctionPointer functionPointer, void *data)
this->functionPointer = functionPointer;
this->data = data;
#if defined Q_OS_UNIX
- const int state = pthread_create(&nativeThreadHandle, 0, NativeThreadWrapper::runUnix, this);
- Q_UNUSED(state);
+ const int state = pthread_create(&nativeThreadHandle, nullptr, NativeThreadWrapper::runUnix, this);
+ Q_UNUSED(state)
#elif defined Q_OS_WIN
unsigned thrdid = 0;
nativeThreadHandle = (Qt::HANDLE) _beginthreadex(NULL, 0, NativeThreadWrapper::runWin, this, 0, &thrdid);
@@ -690,7 +753,7 @@ void NativeThreadWrapper::startAndWait(FunctionPointer functionPointer, void *da
void NativeThreadWrapper::join()
{
#if defined Q_OS_UNIX
- pthread_join(nativeThreadHandle, 0);
+ pthread_join(nativeThreadHandle, nullptr);
#elif defined Q_OS_WIN
WaitForSingleObjectEx(nativeThreadHandle, INFINITE, FALSE);
CloseHandle(nativeThreadHandle);
@@ -720,7 +783,7 @@ void *NativeThreadWrapper::runUnix(void *that)
nativeThreadWrapper->stopCondition.wait(lock.mutex());
}
- return 0;
+ return nullptr;
}
unsigned WIN_FIX_STDCALL NativeThreadWrapper::runWin(void *data)
@@ -736,12 +799,12 @@ void NativeThreadWrapper::stop()
stopCondition.wakeOne();
}
-bool threadAdoptedOk = false;
-QThread *mainThread;
+static bool threadAdoptedOk = false;
+static QThread *mainThread;
void testNativeThreadAdoption(void *)
{
- threadAdoptedOk = (QThread::currentThreadId() != 0
- && QThread::currentThread() != 0
+ threadAdoptedOk = (QThread::currentThreadId() != nullptr
+ && QThread::currentThread() != nullptr
&& QThread::currentThread() != mainThread);
}
void tst_QThread::nativeThreadAdoption()
@@ -769,14 +832,15 @@ void adoptedThreadAffinityFunction(void *arg)
void tst_QThread::adoptedThreadAffinity()
{
- QThread *affinity[2] = { 0, 0 };
+ QThread *affinity[2] = { nullptr, nullptr };
NativeThreadWrapper thread;
thread.startAndWait(adoptedThreadAffinityFunction, affinity);
thread.join();
- // adopted thread should have affinity to itself
- QCOMPARE(affinity[0], affinity[1]);
+ // adopted thread (deleted) should have affinity to itself
+ QCOMPARE(static_cast<const void *>(affinity[0]),
+ static_cast<const void *>(affinity[1]));
}
void tst_QThread::adoptedThreadSetPriority()
@@ -944,10 +1008,24 @@ void tst_QThread::adoptMultipleThreadsOverlap()
QCOMPARE(recorder.activationCount.loadRelaxed(), numThreads);
}
+void tst_QThread::adoptedThreadBindingStatus()
+{
+ NativeThreadWrapper nativeThread;
+ nativeThread.setWaitForStop();
+
+ nativeThread.startAndWait();
+ QVERIFY(nativeThread.qthread);
+ auto privThread = static_cast<QThreadPrivate *>(QObjectPrivate::get(nativeThread.qthread));
+ QVERIFY(privThread->m_statusOrPendingObjects.bindingStatus());
+
+ nativeThread.stop();
+ nativeThread.join();
+}
+
// Disconnects on WinCE
void tst_QThread::stressTest()
{
- if (EmulationDetector::isRunningArmOnX86())
+ if (QTestPrivate::isRunningArmOnX86())
QSKIP("Qemu uses too much memory for each thread. Test would run out of memory.");
QElapsedTimer timer;
@@ -1081,8 +1159,8 @@ void tst_QThread::wait2()
qPrintable(msgElapsed(elapsed)));
}
-
-class SlowSlotObject : public QObject {
+class SlowSlotObject : public QObject
+{
Q_OBJECT
public:
QMutex mutex;
@@ -1098,22 +1176,23 @@ void tst_QThread::wait3_slowDestructor()
{
SlowSlotObject slow;
QThread thread;
- QObject::connect(&thread, SIGNAL(finished()), &slow, SLOT(slowSlot()), Qt::DirectConnection);
-
- enum { WaitTime = 1800 };
+ QObject::connect(&thread, &QThread::finished,
+ &slow, &SlowSlotObject::slowSlot, Qt::DirectConnection);
QElapsedTimer timer;
thread.start();
thread.quit();
- //the quit function will cause the thread to finish and enter the slowSlot that is blocking
+ // Calling quit() will cause the thread to finish and enter the blocking slowSlot().
timer.start();
- QVERIFY(!thread.wait(Waiting_Thread::WaitTime));
- qint64 elapsed = timer.elapsed();
- QVERIFY2(elapsed >= Waiting_Thread::WaitTime - 1, qPrintable(QString::fromLatin1("elapsed: %1").arg(elapsed)));
-
- slow.cond.wakeOne();
- //now the thread should finish quickly
+ {
+ // Ensure thread finishes quickly after the checks - regardless of success:
+ QScopeGuard wakeSlow([&slow]() -> void { slow.cond.wakeOne(); });
+ QVERIFY(!thread.wait(Waiting_Thread::WaitTime));
+ const qint64 elapsed = timer.elapsed();
+ QVERIFY2(elapsed >= Waiting_Thread::WaitTime - 1,
+ qPrintable(QString::fromLatin1("elapsed: %1").arg(elapsed)));
+ }
QVERIFY(thread.wait(one_minute));
}
@@ -1142,7 +1221,7 @@ void tst_QThread::startFinishRace()
void run() override
{
i--;
- if (!i) disconnect(this, SIGNAL(finished()), 0, 0);
+ if (!i) disconnect(this, SIGNAL(finished()), nullptr, nullptr);
}
int i;
};
@@ -1208,13 +1287,10 @@ void tst_QThread::isRunningInFinished()
}
}
-QT_BEGIN_NAMESPACE
-Q_CORE_EXPORT uint qGlobalPostedEventsCount();
-QT_END_NAMESPACE
-
-class DummyEventDispatcher : public QAbstractEventDispatcher {
+class DummyEventDispatcher : public QAbstractEventDispatcherV2
+{
+ Q_OBJECT
public:
- DummyEventDispatcher() : QAbstractEventDispatcher() {}
bool processEvents(QEventLoop::ProcessEventsFlags) override {
visited.storeRelaxed(true);
emit awake();
@@ -1223,11 +1299,11 @@ public:
}
void registerSocketNotifier(QSocketNotifier *) override {}
void unregisterSocketNotifier(QSocketNotifier *) override {}
- void registerTimer(int, qint64, Qt::TimerType, QObject *) override {}
- bool unregisterTimer(int) override { return false; }
+ void registerTimer(Qt::TimerId, Duration, Qt::TimerType, QObject *) override {}
+ bool unregisterTimer(Qt::TimerId) override { return false; }
bool unregisterTimers(QObject *) override { return false; }
- QList<TimerInfo> registeredTimers(QObject *) const override { return QList<TimerInfo>(); }
- int remainingTime(int) override { return 0; }
+ QList<TimerInfoV2> timersForObject(QObject *) const override { return {}; }
+ Duration remainingTime(Qt::TimerId) const override { return 0s; }
void wakeUp() override {}
void interrupt() override {}
@@ -1338,9 +1414,6 @@ void tst_QThread::quitLock()
void tst_QThread::create()
{
-#if !QT_CONFIG(cxx11_future)
- QSKIP("This test requires QThread::create");
-#else
{
const auto &function = [](){};
QScopedPointer<QThread> thread(QThread::create(function));
@@ -1576,11 +1649,70 @@ void tst_QThread::create()
const auto &function = [](const ThrowWhenCopying &){};
QScopedPointer<QThread> thread;
ThrowWhenCopying t;
- QVERIFY_EXCEPTION_THROWN(thread.reset(QThread::create(function, t)), ThreadException);
+ QVERIFY_THROWS_EXCEPTION(ThreadException, thread.reset(QThread::create(function, t)));
QVERIFY(!thread);
}
#endif // QT_NO_EXCEPTIONS
-#endif // QT_CONFIG(cxx11_future)
+}
+
+void tst_QThread::createDestruction()
+{
+ for (int delay : {0, 10, 20}) {
+ auto checkForInterruptions = []() {
+ for (;;) {
+ if (QThread::currentThread()->isInterruptionRequested())
+ return;
+ QThread::sleep(1ms);
+ }
+ };
+
+ QScopedPointer<QThread> thread(QThread::create(checkForInterruptions));
+ QSignalSpy finishedSpy(thread.get(), &QThread::finished);
+ QVERIFY(finishedSpy.isValid());
+
+ thread->start();
+ if (delay)
+ QThread::msleep(delay);
+ thread.reset();
+
+ QCOMPARE(finishedSpy.size(), 1);
+ }
+
+ for (int delay : {0, 10, 20}) {
+ auto runEventLoop = []() {
+ QEventLoop loop;
+ loop.exec();
+ };
+
+ QScopedPointer<QThread> thread(QThread::create(runEventLoop));
+ QSignalSpy finishedSpy(thread.get(), &QThread::finished);
+ QVERIFY(finishedSpy.isValid());
+
+ thread->start();
+ if (delay)
+ QThread::msleep(delay);
+ thread.reset();
+
+ QCOMPARE(finishedSpy.size(), 1);
+ }
+
+ for (int delay : {0, 10, 20}) {
+ auto runEventLoop = [delay]() {
+ if (delay)
+ QThread::msleep(delay);
+ QEventLoop loop;
+ loop.exec();
+ };
+
+ QScopedPointer<QThread> thread(QThread::create(runEventLoop));
+ QSignalSpy finishedSpy(thread.get(), &QThread::finished);
+ QVERIFY(finishedSpy.isValid());
+
+ thread->start();
+ thread.reset();
+
+ QCOMPARE(finishedSpy.size(), 1);
+ }
}
class StopableJob : public QObject
@@ -1621,5 +1753,155 @@ void tst_QThread::requestTermination()
QVERIFY(!thread.isInterruptionRequested());
}
+/*
+ This is a regression test for QTBUG-96846.
+
+ Incorrect system thread ID cleanup can cause QThread::wait() to report that
+ a thread is trying to wait for itself.
+*/
+void tst_QThread::threadIdReuse()
+{
+ // It's important that those thread ID's are not accessed concurrently
+ Qt::HANDLE threadId1;
+
+ auto thread1Fn = [&threadId1]() -> void { threadId1 = QThread::currentThreadId(); };
+ QScopedPointer<QThread> thread1(QThread::create(thread1Fn));
+ thread1->start();
+ QVERIFY(thread1->wait());
+
+ // If the system thread allocated for thread1 is destroyed before thread2 is started,
+ // at least on some versions of Linux the system thread ID for thread2 would be the
+ // same as one that was used for thread1.
+
+ // The system thread may be alive for some time after returning from QThread::wait()
+ // because the implementation is using detachable threads, so some additional time is
+ // required for the system thread to terminate. Not waiting long enough here would result
+ // in a new system thread ID being allocated for thread2 and this test passing even without
+ // a fix for QTBUG-96846.
+ bool threadIdReused = false;
+
+ for (int i = 0; i < 42; i++) {
+ QThread::sleep(1ms);
+
+ Qt::HANDLE threadId2;
+ bool waitOk = false;
+
+ auto waitForThread1 = [&thread1, &threadId2, &waitOk]() -> void {
+ threadId2 = QThread::currentThreadId();
+ waitOk = thread1->wait();
+ };
+
+ QScopedPointer<QThread> thread2(QThread::create(waitForThread1));
+ thread2->start();
+ QVERIFY(thread2->wait());
+ QVERIFY(waitOk);
+
+ if (threadId1 == threadId2) {
+ qDebug("Thread ID reused at iteration %d", i);
+ threadIdReused = true;
+ break;
+ }
+ }
+
+ if (!threadIdReused) {
+ QSKIP("Thread ID was not reused");
+ }
+}
+
+class WaitToRun_Thread : public QThread
+{
+ Q_OBJECT
+public:
+ void run() override
+ {
+ emit running();
+ QThread::exec();
+ }
+
+Q_SIGNALS:
+ void running();
+};
+
+
+void tst_QThread::terminateAndPrematureDestruction()
+{
+#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
+ QSKIP("Thread termination might result in stack underflow address sanitizer errors.");
+#endif
+
+ WaitToRun_Thread thread;
+ QSignalSpy spy(&thread, &WaitToRun_Thread::running);
+ thread.start();
+ QVERIFY(spy.wait(500));
+
+ QScopedPointer<QObject> obj(new QObject);
+ QPointer<QObject> pObj(obj.data());
+ obj->deleteLater();
+
+ thread.terminate();
+ QVERIFY2(pObj, "object was deleted prematurely!");
+ thread.wait(500);
+}
+
+void tst_QThread::terminateAndDoubleDestruction()
+{
+#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
+ QSKIP("Thread termination might result in stack underflow address sanitizer errors.");
+#endif
+
+ class ChildObject : public QObject
+ {
+ public:
+ ChildObject(QObject *parent)
+ : QObject(parent)
+ {
+ QSignalSpy spy(&thread, &WaitToRun_Thread::running);
+ thread.start();
+ spy.wait(500);
+ }
+
+ ~ChildObject()
+ {
+ QVERIFY2(!inDestruction, "Double object destruction!");
+ inDestruction = true;
+ thread.terminate();
+ thread.wait(500);
+ }
+
+ bool inDestruction = false;
+ WaitToRun_Thread thread;
+ };
+
+ class TestObject : public QObject
+ {
+ public:
+ TestObject()
+ : child(new ChildObject(this))
+ {
+ }
+
+ ~TestObject()
+ {
+ child->deleteLater();
+ }
+
+ ChildObject *child = nullptr;
+ };
+
+ TestObject obj;
+}
+
+void tst_QThread::bindingListCleanupAfterDelete()
+{
+ QThread t;
+ auto optr = std::make_unique<QObject>();
+ optr->moveToThread(&t);
+ auto threadPriv = static_cast<QThreadPrivate *>(QObjectPrivate::get(&t));
+ auto list = threadPriv->m_statusOrPendingObjects.list();
+ QVERIFY(list);
+ optr.reset();
+ QVERIFY(list->empty());
+}
+
QTEST_MAIN(tst_QThread)
#include "tst_qthread.moc"