diff options
Diffstat (limited to 'tests/auto/corelib/thread/qthreadpool')
4 files changed, 370 insertions, 167 deletions
diff --git a/tests/auto/corelib/thread/qthreadpool/BLACKLIST b/tests/auto/corelib/thread/qthreadpool/BLACKLIST deleted file mode 100644 index b8c1f3bf3f..0000000000 --- a/tests/auto/corelib/thread/qthreadpool/BLACKLIST +++ /dev/null @@ -1,4 +0,0 @@ -[expiryTimeoutRace] -opensuse-leap -ubuntu -rhel diff --git a/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt b/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt index 8d2c3719c6..fee9c541db 100644 --- a/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt +++ b/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt @@ -1,10 +1,17 @@ -# Generated from qthreadpool.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qthreadpool Test: ##################################################################### -qt_add_test(tst_qthreadpool +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qthreadpool LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qthreadpool SOURCES tst_qthreadpool.cpp ) diff --git a/tests/auto/corelib/thread/qthreadpool/qthreadpool.pro b/tests/auto/corelib/thread/qthreadpool/qthreadpool.pro deleted file mode 100644 index eac4ef9365..0000000000 --- a/tests/auto/corelib/thread/qthreadpool/qthreadpool.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qthreadpool -QT = core testlib -SOURCES = tst_qthreadpool.cpp diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 9b536aec00..2006016d47 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -1,33 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QSemaphore> + #include <qelapsedtimer.h> +#include <qrunnable.h> #include <qthreadpool.h> #include <qstring.h> #include <qmutex.h> @@ -36,6 +15,8 @@ #include <unistd.h> #endif +using namespace std::chrono_literals; + typedef void (*FunctionPointer)(); class FunctionPointerTask : public QRunnable @@ -65,6 +46,7 @@ public: private slots: void runFunction(); void runFunction2(); + void runFunction3(); void createThreadRunFunction(); void runMultiple(); void waitcomplete(); @@ -72,6 +54,7 @@ private slots: void singleton(); void destruction(); void threadRecycling(); + void threadPriority(); void expiryTimeout(); void expiryTimeoutRace(); #ifndef QT_NO_EXCEPTIONS @@ -85,6 +68,8 @@ private slots: void releaseThread_data(); void releaseThread(); void reserveAndStart(); + void reserveAndStart2(); + void releaseAndBlock(); void start(); void tryStart(); void tryStartPeakThreadCount(); @@ -93,6 +78,7 @@ private slots: void priorityStart(); void waitForDone(); void clear(); + void clearWithAutoDelete(); void tryTake(); void waitForDoneTimeout(); void destroyingWaitsForTasksToFinish(); @@ -100,13 +86,15 @@ private slots: void stressTest(); void takeAllAndIncreaseMaxThreadCount(); void waitForDoneAfterTake(); + void threadReuse(); + void nullFunctions(); private: QMutex m_functionTestMutex; }; -QMutex *tst_QThreadPool::functionTestMutex = 0; +QMutex *tst_QThreadPool::functionTestMutex = nullptr; tst_QThreadPool::tst_QThreadPool() { @@ -115,10 +103,10 @@ tst_QThreadPool::tst_QThreadPool() tst_QThreadPool::~tst_QThreadPool() { - tst_QThreadPool::functionTestMutex = 0; + tst_QThreadPool::functionTestMutex = nullptr; } -int testFunctionCount; +static int testFunctionCount; void sleepTestFunction() { @@ -153,10 +141,25 @@ void noSleepTestFunctionMutex() tst_QThreadPool::functionTestMutex->unlock(); } +constexpr int DefaultWaitForDoneTimeout = 1 * 60 * 1000; // 1min +// Using qFatal instead of QVERIFY to force exit if threads are still running after timeout. +// Otherwise, QCoreApplication will still wait for the stale threads and never exit the test. +#define WAIT_FOR_DONE(manager) \ + if ((manager).waitForDone(DefaultWaitForDoneTimeout)) {} else \ + qFatal("waitForDone returned false. Aborting to stop background threads.") + +// uses explicit timeout in dtor's waitForDone() to avoid tests hanging overly long +class TestThreadPool : public QThreadPool +{ +public: + using QThreadPool::QThreadPool; + ~TestThreadPool() { WAIT_FOR_DONE(*this); } +}; + void tst_QThreadPool::runFunction() { { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; manager.start(noSleepTestFunction); } @@ -167,16 +170,33 @@ void tst_QThreadPool::runFunction2() { int localCount = 0; { - QThreadPool manager; + TestThreadPool manager; manager.start([&]() { ++localCount; }); } QCOMPARE(localCount, 1); } +struct DeleteCheck +{ + static bool s_deleted; + ~DeleteCheck() { s_deleted = true; } +}; +bool DeleteCheck::s_deleted = false; + +void tst_QThreadPool::runFunction3() +{ + std::unique_ptr<DeleteCheck> ptr(new DeleteCheck); + { + TestThreadPool manager; + manager.start([my_ptr = std::move(ptr)]() { }); + } + QVERIFY(DeleteCheck::s_deleted); +} + void tst_QThreadPool::createThreadRunFunction() { { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; manager.start(noSleepTestFunction); } @@ -189,7 +209,7 @@ void tst_QThreadPool::runMultiple() const int runs = 10; { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; for (int i = 0; i < runs; ++i) { manager.start(sleepTestFunctionMutex); @@ -198,7 +218,7 @@ void tst_QThreadPool::runMultiple() QCOMPARE(testFunctionCount, runs); { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; for (int i = 0; i < runs; ++i) { manager.start(noSleepTestFunctionMutex); @@ -207,7 +227,7 @@ void tst_QThreadPool::runMultiple() QCOMPARE(testFunctionCount, runs); { - QThreadPool manager; + TestThreadPool manager; for (int i = 0; i < 500; ++i) manager.start(emptyFunct); } @@ -218,13 +238,14 @@ void tst_QThreadPool::waitcomplete() testFunctionCount = 0; const int runs = 500; for (int i = 0; i < 500; ++i) { + // TestThreadPool pool; // no, we're checking ~QThreadPool()'s waitForDone() QThreadPool pool; pool.start(noSleepTestFunction); } QCOMPARE(testFunctionCount, runs); } -QAtomicInt ran; // bool +static QAtomicInt ran; // bool class TestTask : public QRunnable { public: @@ -236,7 +257,7 @@ public: void tst_QThreadPool::runTask() { - QThreadPool manager; + TestThreadPool manager; ran.storeRelaxed(false); manager.start(new TestTask()); QTRY_VERIFY(ran.loadRelaxed()); @@ -252,7 +273,7 @@ void tst_QThreadPool::singleton() QTRY_VERIFY(ran.loadRelaxed()); } -QAtomicInt *value = 0; +static QAtomicInt *value = nullptr; class IntAccessor : public QRunnable { public: @@ -277,11 +298,11 @@ void tst_QThreadPool::destruction() threadManager->start(new IntAccessor()); delete threadManager; delete value; - value = 0; + value = nullptr; } -QSemaphore threadRecyclingSemaphore; -QThread *recycledThread = 0; +static QSemaphore threadRecyclingSemaphore; +static QThread *recycledThread = nullptr; class ThreadRecorderTask : public QRunnable { @@ -298,7 +319,7 @@ public: */ void tst_QThreadPool::threadRecycling() { - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.start(new ThreadRecorderTask()); threadRecyclingSemaphore.acquire(); @@ -319,6 +340,25 @@ void tst_QThreadPool::threadRecycling() QCOMPARE(thread2, thread3); } +/* + Test that the thread priority from the thread created by the pool matches + the one configured on the pool. +*/ +void tst_QThreadPool::threadPriority() +{ + QThread::Priority priority = QThread::HighPriority; + TestThreadPool threadPool; + threadPool.setThreadPriority(priority); + + threadPool.start(new ThreadRecorderTask()); + threadRecyclingSemaphore.acquire(); + QThread *thread = recycledThread; + + QTest::qSleep(100); + + QCOMPARE(thread->priority(), priority); +} + class ExpiryTimeoutTask : public QRunnable { public: @@ -327,7 +367,7 @@ public: QSemaphore semaphore; ExpiryTimeoutTask() - : thread(0), runCount(0) + : thread(nullptr), runCount(0) { setAutoDelete(false); } @@ -344,7 +384,7 @@ void tst_QThreadPool::expiryTimeout() { ExpiryTimeoutTask task; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); int expiryTimeout = threadPool.expiryTimeout(); @@ -378,18 +418,15 @@ void tst_QThreadPool::expiryTimeout() void tst_QThreadPool::expiryTimeoutRace() // QTBUG-3786 { -#ifdef Q_OS_WIN - QSKIP("This test is unstable on Windows. See QTBUG-3786."); -#endif ExpiryTimeoutTask task; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); threadPool.setExpiryTimeout(50); const int numTasks = 20; for (int i = 0; i < numTasks; ++i) { threadPool.start(&task); - QThread::msleep(50); // exactly the same as the expiry timeout + QThread::sleep(50ms); // exactly the same as the expiry timeout } QVERIFY(task.semaphore.tryAcquire(numTasks, 10000)); QCOMPARE(task.runCount.loadRelaxed(), numTasks); @@ -400,7 +437,7 @@ void tst_QThreadPool::expiryTimeoutRace() // QTBUG-3786 class ExceptionTask : public QRunnable { public: - void run() + void run() override { throw new int; } @@ -410,7 +447,7 @@ void tst_QThreadPool::exceptions() { ExceptionTask task; { - QThreadPool threadPool; + TestThreadPool threadPool; // Uncomment this for a nice crash. // threadPool.start(&task); } @@ -439,6 +476,9 @@ void tst_QThreadPool::setMaxThreadCount() QFETCH(int, limit); QThreadPool *threadPool = QThreadPool::globalInstance(); int savedLimit = threadPool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadPool->setMaxThreadCount(savedLimit); + }); // maxThreadCount() should always return the previous argument to // setMaxThreadCount(), regardless of input @@ -451,7 +491,7 @@ void tst_QThreadPool::setMaxThreadCount() // setting the limit on children should have no effect on the parent { - QThreadPool threadPool2(threadPool); + TestThreadPool threadPool2(threadPool); savedLimit = threadPool2.maxThreadCount(); // maxThreadCount() should always return the previous argument to @@ -481,58 +521,57 @@ void tst_QThreadPool::setMaxThreadCountStartsAndStopsThreads() } }; - QThreadPool threadPool; - threadPool.setMaxThreadCount(1); + TestThreadPool threadPool; + threadPool.setMaxThreadCount(-1); // docs say we'll always start at least one - WaitingTask *task = new WaitingTask; - threadPool.start(task); - QVERIFY(task->waitForStarted.tryAcquire(1, 1000)); + WaitingTask task; + threadPool.start(&task); + QVERIFY(task.waitForStarted.tryAcquire(1, 1000)); // thread limit is 1, cannot start more tasks - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(1, 1000)); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(1, 1000)); // increasing the limit by 1 should start the task immediately threadPool.setMaxThreadCount(2); - QVERIFY(task->waitForStarted.tryAcquire(1, 1000)); + QVERIFY(task.waitForStarted.tryAcquire(1, 1000)); // ... but we still cannot start more tasks - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(1, 1000)); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(1, 1000)); // increasing the limit should be able to start more than one at a time - threadPool.start(task); + threadPool.start(&task); threadPool.setMaxThreadCount(4); - QVERIFY(task->waitForStarted.tryAcquire(2, 1000)); + QVERIFY(task.waitForStarted.tryAcquire(2, 1000)); // ... but we still cannot start more tasks - threadPool.start(task); - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(2, 1000)); + threadPool.start(&task); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(2, 1000)); // decreasing the thread limit should cause the active thread count to go down threadPool.setMaxThreadCount(2); QCOMPARE(threadPool.activeThreadCount(), 4); - task->waitToFinish.release(2); + task.waitToFinish.release(2); QTest::qWait(1000); QCOMPARE(threadPool.activeThreadCount(), 2); // ... and we still cannot start more tasks - threadPool.start(task); - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(2, 1000)); + threadPool.start(&task); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(2, 1000)); // start all remaining tasks - threadPool.start(task); - threadPool.start(task); - threadPool.start(task); - threadPool.start(task); + threadPool.start(&task); + threadPool.start(&task); + threadPool.start(&task); + threadPool.start(&task); threadPool.setMaxThreadCount(8); - QVERIFY(task->waitForStarted.tryAcquire(6, 1000)); + QVERIFY(task.waitForStarted.tryAcquire(6, 1000)); - task->waitToFinish.release(10); + task.waitToFinish.release(10); threadPool.waitForDone(); - delete task; } void tst_QThreadPool::reserveThread_data() @@ -544,7 +583,11 @@ void tst_QThreadPool::reserveThread() { QFETCH(int, limit); QThreadPool *threadpool = QThreadPool::globalInstance(); - int savedLimit = threadpool->maxThreadCount(); + const int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + threadpool->setMaxThreadCount(limit); // reserve up to the limit @@ -566,7 +609,7 @@ void tst_QThreadPool::reserveThread() // reserving threads in children should not effect the parent { - QThreadPool threadpool2(threadpool); + TestThreadPool threadpool2(threadpool); threadpool2.setMaxThreadCount(limit); // reserve up to the limit @@ -593,9 +636,6 @@ void tst_QThreadPool::reserveThread() while (threadpool2.activeThreadCount() > 0) threadpool2.releaseThread(); } - - // reset limit on global QThreadPool - threadpool->setMaxThreadCount(savedLimit); } void tst_QThreadPool::releaseThread_data() @@ -607,7 +647,10 @@ void tst_QThreadPool::releaseThread() { QFETCH(int, limit); QThreadPool *threadpool = QThreadPool::globalInstance(); - int savedLimit = threadpool->maxThreadCount(); + const int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); threadpool->setMaxThreadCount(limit); // reserve up to the limit @@ -630,7 +673,7 @@ void tst_QThreadPool::releaseThread() // releasing threads in children should not effect the parent { - QThreadPool threadpool2(threadpool); + TestThreadPool threadpool2(threadpool); threadpool2.setMaxThreadCount(limit); // reserve up to the limit @@ -655,9 +698,6 @@ void tst_QThreadPool::releaseThread() QCOMPARE(threadpool2.activeThreadCount(), 0); QCOMPARE(threadpool->activeThreadCount(), 0); } - - // reset limit on global QThreadPool - threadpool->setMaxThreadCount(savedLimit); } void tst_QThreadPool::reserveAndStart() // QTBUG-21051 @@ -682,6 +722,10 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051 // Set up QThreadPool *threadpool = QThreadPool::globalInstance(); int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + threadpool->setMaxThreadCount(1); QCOMPARE(threadpool->activeThreadCount(), 0); @@ -689,37 +733,133 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051 threadpool->reserveThread(); QCOMPARE(threadpool->activeThreadCount(), 1); - // start a task, to get a running thread - WaitingTask *task = new WaitingTask; - threadpool->start(task); + // start a task, to get a running thread, works since one thread is always allowed + WaitingTask task; + threadpool->start(&task); QCOMPARE(threadpool->activeThreadCount(), 2); - task->waitForStarted.acquire(); - task->waitBeforeDone.release(); - QTRY_COMPARE(task->count.loadRelaxed(), 1); + // tryStart() will fail since activeThreadCount() >= maxThreadCount() and one thread is already running + QVERIFY(!threadpool->tryStart(&task)); + QTRY_COMPARE(threadpool->activeThreadCount(), 2); + task.waitForStarted.acquire(); + task.waitBeforeDone.release(); + QTRY_COMPARE(task.count.loadRelaxed(), 1); QTRY_COMPARE(threadpool->activeThreadCount(), 1); - // now the thread is waiting, but tryStart() will fail since activeThreadCount() >= maxThreadCount() - QVERIFY(!threadpool->tryStart(task)); + // start() will wake up the waiting thread. + threadpool->start(&task); + QTRY_COMPARE(threadpool->activeThreadCount(), 2); + QTRY_COMPARE(task.count.loadRelaxed(), 2); + WaitingTask task2; + // startOnReservedThread() will try to take the reserved task, but end up waiting instead + threadpool->startOnReservedThread(&task2); + QTRY_COMPARE(threadpool->activeThreadCount(), 1); + task.waitForStarted.acquire(); + task.waitBeforeDone.release(); QTRY_COMPARE(threadpool->activeThreadCount(), 1); + task2.waitForStarted.acquire(); + task2.waitBeforeDone.release(); - // start() will therefore do a failing tryStart(), followed by enqueueTask() - // which will actually wake up the waiting thread. - threadpool->start(task); - QTRY_COMPARE(threadpool->activeThreadCount(), 2); - task->waitForStarted.acquire(); - task->waitBeforeDone.release(); - QTRY_COMPARE(task->count.loadRelaxed(), 2); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); +} + +void tst_QThreadPool::reserveAndStart2() +{ + class WaitingTask : public QRunnable + { + public: + QSemaphore waitBeforeDone; + + WaitingTask() { setAutoDelete(false); } + + void run() override + { + waitBeforeDone.acquire(); + } + }; + + // Set up + QThreadPool *threadpool = QThreadPool::globalInstance(); + int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + threadpool->setMaxThreadCount(2); + + // reserve + threadpool->reserveThread(); + + // start two task, to get a running thread and one queued + WaitingTask task1, task2, task3; + threadpool->start(&task1); + // one running thread, one reserved: + QCOMPARE(threadpool->activeThreadCount(), 2); + // task2 starts queued + threadpool->start(&task2); + QCOMPARE(threadpool->activeThreadCount(), 2); + // startOnReservedThread() will take the reserved thread however, bypassing the queue + threadpool->startOnReservedThread(&task3); + // two running threads, none reserved: + QCOMPARE(threadpool->activeThreadCount(), 2); + task3.waitBeforeDone.release(); + // task3 can finish even if all other tasks are blocking + // then task2 will use the previously reserved thread + task2.waitBeforeDone.release(); QTRY_COMPARE(threadpool->activeThreadCount(), 1); + task1.waitBeforeDone.release(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); +} + +void tst_QThreadPool::releaseAndBlock() +{ + class WaitingTask : public QRunnable + { + public: + QSemaphore waitBeforeDone; + + WaitingTask() { setAutoDelete(false); } + + void run() override + { + waitBeforeDone.acquire(); + } + }; + + // Set up + QThreadPool *threadpool = QThreadPool::globalInstance(); + const int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + + threadpool->setMaxThreadCount(1); + QCOMPARE(threadpool->activeThreadCount(), 0); + + // start a task, to get a running thread, works since one thread is always allowed + WaitingTask task1, task2; + threadpool->start(&task1); + QCOMPARE(threadpool->activeThreadCount(), 1); + // tryStart() will fail since activeThreadCount() >= maxThreadCount() and one thread is already running + QVERIFY(!threadpool->tryStart(&task2)); + QCOMPARE(threadpool->activeThreadCount(), 1); + + // Use release without reserve to account for the blocking thread. threadpool->releaseThread(); QTRY_COMPARE(threadpool->activeThreadCount(), 0); - delete task; + // Now we can start task2 + QVERIFY(threadpool->tryStart(&task2)); + QCOMPARE(threadpool->activeThreadCount(), 1); + task2.waitBeforeDone.release(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); - threadpool->setMaxThreadCount(savedLimit); + threadpool->reserveThread(); + QCOMPARE(threadpool->activeThreadCount(), 1); + task1.waitBeforeDone.release(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); } -QAtomicInt count; +static QAtomicInt count; class CountingRunnable : public QRunnable { public: @@ -734,7 +874,7 @@ void tst_QThreadPool::start() const int runs = 1000; count.storeRelaxed(0); { - QThreadPool threadPool; + TestThreadPool threadPool; for (int i = 0; i< runs; ++i) { threadPool.start(new CountingRunnable()); } @@ -761,19 +901,19 @@ void tst_QThreadPool::tryStart() count.storeRelaxed(0); WaitingTask task; - QThreadPool threadPool; + TestThreadPool threadPool; for (int i = 0; i < threadPool.maxThreadCount(); ++i) { threadPool.start(&task); } QVERIFY(!threadPool.tryStart(&task)); task.semaphore.release(threadPool.maxThreadCount()); - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount()); } -QMutex mutex; -QAtomicInt activeThreads; -QAtomicInt peakActiveThreads; +static QMutex mutex; +static QAtomicInt activeThreads; +static QAtomicInt peakActiveThreads; void tst_QThreadPool::tryStartPeakThreadCount() { class CounterTask : public QRunnable @@ -798,7 +938,7 @@ void tst_QThreadPool::tryStartPeakThreadCount() }; CounterTask task; - QThreadPool threadPool; + TestThreadPool threadPool; for (int i = 0; i < 4*QThread::idealThreadCount(); ++i) { if (threadPool.tryStart(&task) == false) @@ -827,7 +967,7 @@ void tst_QThreadPool::tryStartCount() }; SleeperTask task; - QThreadPool threadPool; + TestThreadPool threadPool; const int runs = 5; for (int i = 0; i < runs; ++i) { @@ -867,7 +1007,7 @@ void tst_QThreadPool::priorityStart() Runner(QAtomicPointer<QRunnable> &ptr) : ptr(ptr) {} void run() override { - ptr.testAndSetRelaxed(0, this); + ptr.testAndSetRelaxed(nullptr, this); } }; @@ -875,7 +1015,7 @@ void tst_QThreadPool::priorityStart() QSemaphore sem; QAtomicPointer<QRunnable> firstStarted; QRunnable *expected; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); // start only one thread at a time // queue the holder first @@ -887,7 +1027,7 @@ void tst_QThreadPool::priorityStart() threadPool.start(expected = new Runner(firstStarted), 1); // priority 1 sem.release(); - QVERIFY(threadPool.waitForDone()); + WAIT_FOR_DONE(threadPool); QCOMPARE(firstStarted.loadRelaxed(), expected); } @@ -895,8 +1035,9 @@ void tst_QThreadPool::waitForDone() { QElapsedTimer total, pass; total.start(); + pass.start(); - QThreadPool threadPool; + TestThreadPool threadPool; while (total.elapsed() < 10000) { int runs; count.storeRelaxed(runs = 0); @@ -905,7 +1046,7 @@ void tst_QThreadPool::waitForDone() threadPool.start(new CountingRunnable()); ++runs; } - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(count.loadRelaxed(), runs); count.storeRelaxed(runs = 0); @@ -915,7 +1056,7 @@ void tst_QThreadPool::waitForDone() threadPool.start(new CountingRunnable()); runs += 2; } - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(count.loadRelaxed(), runs); } } @@ -937,7 +1078,7 @@ void tst_QThreadPool::waitForDoneTimeout() } }; - QThreadPool threadPool; + TestThreadPool threadPool; mutex.lock(); threadPool.start(new BlockedTask(mutex)); @@ -961,7 +1102,7 @@ void tst_QThreadPool::clear() } }; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(10); int runs = 2 * threadPool.maxThreadCount(); count.storeRelaxed(0); @@ -970,10 +1111,34 @@ void tst_QThreadPool::clear() } threadPool.clear(); sem.release(threadPool.maxThreadCount()); - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount()); } +void tst_QThreadPool::clearWithAutoDelete() +{ + class MyRunnable : public QRunnable + { + public: + MyRunnable() {} + void run() override { QThread::sleep(30us); } + }; + + TestThreadPool threadPool; + threadPool.setMaxThreadCount(4); + const int loopCount = 20; + const int batchSize = 500; + // Should not crash see QTBUG-87092 + for (int i = 0; i < loopCount; i++) { + threadPool.clear(); + for (int j = 0; j < batchSize; j++) { + auto *runnable = new MyRunnable(); + runnable->setAutoDelete(true); + threadPool.start(runnable); + } + } +} + void tst_QThreadPool::tryTake() { QSemaphore sem(0); @@ -991,7 +1156,7 @@ void tst_QThreadPool::tryTake() explicit BlockingRunnable(QSemaphore &s, QSemaphore &started, QAtomicInt &c, QAtomicInt &r) : sem(s), startedThreads(started), dtorCounter(c), runCounter(r) {} - ~BlockingRunnable() + ~BlockingRunnable() override { dtorCounter.fetchAndAddRelaxed(1); } @@ -1011,7 +1176,7 @@ void tst_QThreadPool::tryTake() Runs = MaxThreadCount * OverProvisioning }; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(MaxThreadCount); BlockingRunnable *runnables[Runs]; @@ -1043,7 +1208,7 @@ void tst_QThreadPool::tryTake() runnables[0]->dummy = 0; // valgrind will catch this if tryTake() is crazy enough to delete currently running jobs QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - MaxThreadCount)); sem.release(MaxThreadCount); - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(runCounter.loadRelaxed(), int(MaxThreadCount)); QCOMPARE(count.loadRelaxed(), int(MaxThreadCount)); QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - 1)); @@ -1054,12 +1219,13 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish() { QElapsedTimer total, pass; total.start(); + pass.start(); while (total.elapsed() < 10000) { int runs; count.storeRelaxed(runs = 0); { - QThreadPool threadPool; + TestThreadPool threadPool; pass.restart(); while (pass.elapsed() < 100) { threadPool.start(new CountingRunnable()); @@ -1070,7 +1236,7 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish() count.storeRelaxed(runs = 0); { - QThreadPool threadPool; + TestThreadPool threadPool; pass.restart(); while (pass.elapsed() < 100) { threadPool.start(new CountingRunnable()); @@ -1112,10 +1278,10 @@ void tst_QThreadPool::stackSize() } }; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setStackSize(targetStackSize); threadPool.start(new StackSizeChecker(&threadStackSize)); - QVERIFY(threadPool.waitForDone(30000)); // 30s timeout + WAIT_FOR_DONE(threadPool); QCOMPARE(threadStackSize, targetStackSize); } @@ -1176,24 +1342,24 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() { QSemaphore mainBarrier; QSemaphore taskBarrier; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); - Task *task1 = new Task(&mainBarrier, &taskBarrier); - Task *task2 = new Task(&mainBarrier, &taskBarrier); - Task *task3 = new Task(&mainBarrier, &taskBarrier); + Task task1(&mainBarrier, &taskBarrier); + Task task2(&mainBarrier, &taskBarrier); + Task task3(&mainBarrier, &taskBarrier); - threadPool.start(task1); - threadPool.start(task2); - threadPool.start(task3); + threadPool.start(&task1); + threadPool.start(&task2); + threadPool.start(&task3); mainBarrier.acquire(1); QCOMPARE(threadPool.activeThreadCount(), 1); - QVERIFY(!threadPool.tryTake(task1)); - QVERIFY(threadPool.tryTake(task2)); - QVERIFY(threadPool.tryTake(task3)); + QVERIFY(!threadPool.tryTake(&task1)); + QVERIFY(threadPool.tryTake(&task2)); + QVERIFY(threadPool.tryTake(&task3)); // A bad queue implementation can segfault here because two consecutive items in the queue // have been taken @@ -1207,13 +1373,9 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() { taskBarrier.release(1); - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(threadPool.activeThreadCount(), 0); - - delete task1; - delete task2; - delete task3; } void tst_QThreadPool::waitForDoneAfterTake() @@ -1244,7 +1406,7 @@ void tst_QThreadPool::waitForDoneAfterTake() // Blocks the tasks from completing their run function QSemaphore threadBarrier; - QThreadPool manager; + TestThreadPool manager; manager.setMaxThreadCount(threadCount); // Fill all the threads with runnables that wait for the threadBarrier @@ -1259,10 +1421,9 @@ void tst_QThreadPool::waitForDoneAfterTake() // This sets the queue elements to nullptr in QThreadPool and we want to test that // the threads keep going through the queue after encountering a nullptr. for (int i = 0; i < threadCount; i++) { - QRunnable *runnable = createTask(emptyFunct); - manager.start(runnable); - QVERIFY(manager.tryTake(runnable)); - delete runnable; + QScopedPointer<QRunnable> runnable(createTask(emptyFunct)); + manager.start(runnable.get()); + QVERIFY(manager.tryTake(runnable.get())); } // Add another runnable that will not be removed @@ -1276,12 +1437,55 @@ void tst_QThreadPool::waitForDoneAfterTake() // Release runnables that are waiting and expect all runnables to complete threadBarrier.release(threadCount); +} - // Using qFatal instead of QVERIFY to force exit if threads are still running after timeout. - // Otherwise, QCoreApplication will still wait for the stale threads and never exit the test. - if (!manager.waitForDone(5 * 60 * 1000)) - qFatal("waitForDone returned false. Aborting to stop background threads."); +/* + Try trigger reuse of expired threads and check that all tasks execute. + + This is a regression test for QTBUG-72872. +*/ +void tst_QThreadPool::threadReuse() +{ + TestThreadPool manager; + manager.setExpiryTimeout(-1); + manager.setMaxThreadCount(1); + constexpr int repeatCount = 10000; + constexpr int timeoutMs = 1000; + QSemaphore sem; + + for (int i = 0; i < repeatCount; i++) { + manager.start([&sem]() { sem.release(); }); + manager.start([&sem]() { sem.release(); }); + manager.releaseThread(); + QVERIFY(sem.tryAcquire(2, timeoutMs)); + manager.reserveThread(); + } +} + +void tst_QThreadPool::nullFunctions() +{ + const auto expectWarning = [] { + QTest::ignoreMessage(QtMsgType::QtWarningMsg, + "Trying to create null QRunnable. This may stop working."); + }; + // Note this is not necessarily testing intended behavior, only undocumented behavior. + // If this is changed it should be noted in Behavioral Changes. + FunctionPointer nullFunction = nullptr; + std::function<void()> nullStdFunction(nullptr); + { + TestThreadPool manager; + // should not crash: + expectWarning(); + manager.start(nullFunction); + expectWarning(); + manager.start(nullStdFunction); + // should fail (and not leak): + expectWarning(); + QVERIFY(!manager.tryStart(nullStdFunction)); + expectWarning(); + QVERIFY(!manager.tryStart(nullFunction)); + } } QTEST_MAIN(tst_QThreadPool); |