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.cpp309
1 files changed, 263 insertions, 46 deletions
diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
index 0631070d22..a7194d4442 100644
--- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp
+++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
@@ -1,36 +1,14 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 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$
-**
-****************************************************************************/
+// 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>
@@ -41,12 +19,14 @@
#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
@@ -58,6 +38,8 @@
#include <QtTest/private/qemulationdetector_p.h>
+using namespace std::chrono_literals;
+
class tst_QThread : public QObject
{
Q_OBJECT
@@ -90,6 +72,7 @@ private slots:
void adoptedThreadExecFinished();
void adoptMultipleThreads();
void adoptMultipleThreadsOverlap();
+ void adoptedThreadBindingStatus();
void exitAndStart();
void exitAndExec();
@@ -111,6 +94,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 };
@@ -257,17 +247,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();
@@ -479,6 +471,10 @@ 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);
@@ -545,6 +541,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);
@@ -950,6 +950,20 @@ 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()
{
@@ -1215,13 +1229,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();
@@ -1230,11 +1241,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 {}
@@ -1345,9 +1356,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));
@@ -1583,11 +1591,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
@@ -1628,5 +1695,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"