tst_QThreadPool: reduce the number of infinite timeouts

... by supplying a TestThreadPool that waitsForDone() with a defined
timeout in the dtor.

When tests hang, this will now print an intelligible message instead
of just the generic watchdog-killed-process one.

Also replace all QVERIFY(waitForDone()) with the same code used in
TestThreadPool's dtor and add a comment in a place we'd rather not use
these tools.

Pick-to: 6.5 6.2
Change-Id: Ifd2b3372eb7c7337a3ba77d003e45dcd77e23545
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Marc Mutz 2023-03-29 09:24:24 +02:00
parent eb260c132a
commit 1cc1d39f3e

View File

@ -138,10 +138,25 @@ void noSleepTestFunctionMutex()
tst_QThreadPool::functionTestMutex->unlock(); 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() void tst_QThreadPool::runFunction()
{ {
{ {
QThreadPool manager; TestThreadPool manager;
testFunctionCount = 0; testFunctionCount = 0;
manager.start(noSleepTestFunction); manager.start(noSleepTestFunction);
} }
@ -152,7 +167,7 @@ void tst_QThreadPool::runFunction2()
{ {
int localCount = 0; int localCount = 0;
{ {
QThreadPool manager; TestThreadPool manager;
manager.start([&]() { ++localCount; }); manager.start([&]() { ++localCount; });
} }
QCOMPARE(localCount, 1); QCOMPARE(localCount, 1);
@ -161,7 +176,7 @@ void tst_QThreadPool::runFunction2()
void tst_QThreadPool::createThreadRunFunction() void tst_QThreadPool::createThreadRunFunction()
{ {
{ {
QThreadPool manager; TestThreadPool manager;
testFunctionCount = 0; testFunctionCount = 0;
manager.start(noSleepTestFunction); manager.start(noSleepTestFunction);
} }
@ -174,7 +189,7 @@ void tst_QThreadPool::runMultiple()
const int runs = 10; const int runs = 10;
{ {
QThreadPool manager; TestThreadPool manager;
testFunctionCount = 0; testFunctionCount = 0;
for (int i = 0; i < runs; ++i) { for (int i = 0; i < runs; ++i) {
manager.start(sleepTestFunctionMutex); manager.start(sleepTestFunctionMutex);
@ -183,7 +198,7 @@ void tst_QThreadPool::runMultiple()
QCOMPARE(testFunctionCount, runs); QCOMPARE(testFunctionCount, runs);
{ {
QThreadPool manager; TestThreadPool manager;
testFunctionCount = 0; testFunctionCount = 0;
for (int i = 0; i < runs; ++i) { for (int i = 0; i < runs; ++i) {
manager.start(noSleepTestFunctionMutex); manager.start(noSleepTestFunctionMutex);
@ -192,7 +207,7 @@ void tst_QThreadPool::runMultiple()
QCOMPARE(testFunctionCount, runs); QCOMPARE(testFunctionCount, runs);
{ {
QThreadPool manager; TestThreadPool manager;
for (int i = 0; i < 500; ++i) for (int i = 0; i < 500; ++i)
manager.start(emptyFunct); manager.start(emptyFunct);
} }
@ -203,6 +218,7 @@ void tst_QThreadPool::waitcomplete()
testFunctionCount = 0; testFunctionCount = 0;
const int runs = 500; const int runs = 500;
for (int i = 0; i < 500; ++i) { for (int i = 0; i < 500; ++i) {
// TestThreadPool pool; // no, we're checking ~QThreadPool()'s waitForDone()
QThreadPool pool; QThreadPool pool;
pool.start(noSleepTestFunction); pool.start(noSleepTestFunction);
} }
@ -221,7 +237,7 @@ public:
void tst_QThreadPool::runTask() void tst_QThreadPool::runTask()
{ {
QThreadPool manager; TestThreadPool manager;
ran.storeRelaxed(false); ran.storeRelaxed(false);
manager.start(new TestTask()); manager.start(new TestTask());
QTRY_VERIFY(ran.loadRelaxed()); QTRY_VERIFY(ran.loadRelaxed());
@ -283,7 +299,7 @@ public:
*/ */
void tst_QThreadPool::threadRecycling() void tst_QThreadPool::threadRecycling()
{ {
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.start(new ThreadRecorderTask()); threadPool.start(new ThreadRecorderTask());
threadRecyclingSemaphore.acquire(); threadRecyclingSemaphore.acquire();
@ -311,7 +327,7 @@ void tst_QThreadPool::threadRecycling()
void tst_QThreadPool::threadPriority() void tst_QThreadPool::threadPriority()
{ {
QThread::Priority priority = QThread::HighPriority; QThread::Priority priority = QThread::HighPriority;
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setThreadPriority(priority); threadPool.setThreadPriority(priority);
threadPool.start(new ThreadRecorderTask()); threadPool.start(new ThreadRecorderTask());
@ -348,7 +364,7 @@ void tst_QThreadPool::expiryTimeout()
{ {
ExpiryTimeoutTask task; ExpiryTimeoutTask task;
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(1); threadPool.setMaxThreadCount(1);
int expiryTimeout = threadPool.expiryTimeout(); int expiryTimeout = threadPool.expiryTimeout();
@ -387,7 +403,7 @@ void tst_QThreadPool::expiryTimeoutRace() // QTBUG-3786
#endif #endif
ExpiryTimeoutTask task; ExpiryTimeoutTask task;
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(1); threadPool.setMaxThreadCount(1);
threadPool.setExpiryTimeout(50); threadPool.setExpiryTimeout(50);
const int numTasks = 20; const int numTasks = 20;
@ -414,7 +430,7 @@ void tst_QThreadPool::exceptions()
{ {
ExceptionTask task; ExceptionTask task;
{ {
QThreadPool threadPool; TestThreadPool threadPool;
// Uncomment this for a nice crash. // Uncomment this for a nice crash.
// threadPool.start(&task); // threadPool.start(&task);
} }
@ -458,7 +474,7 @@ void tst_QThreadPool::setMaxThreadCount()
// setting the limit on children should have no effect on the parent // setting the limit on children should have no effect on the parent
{ {
QThreadPool threadPool2(threadPool); TestThreadPool threadPool2(threadPool);
savedLimit = threadPool2.maxThreadCount(); savedLimit = threadPool2.maxThreadCount();
// maxThreadCount() should always return the previous argument to // maxThreadCount() should always return the previous argument to
@ -488,7 +504,7 @@ void tst_QThreadPool::setMaxThreadCountStartsAndStopsThreads()
} }
}; };
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(-1); // docs say we'll always start at least one threadPool.setMaxThreadCount(-1); // docs say we'll always start at least one
WaitingTask task; WaitingTask task;
@ -576,7 +592,7 @@ void tst_QThreadPool::reserveThread()
// reserving threads in children should not effect the parent // reserving threads in children should not effect the parent
{ {
QThreadPool threadpool2(threadpool); TestThreadPool threadpool2(threadpool);
threadpool2.setMaxThreadCount(limit); threadpool2.setMaxThreadCount(limit);
// reserve up to the limit // reserve up to the limit
@ -640,7 +656,7 @@ void tst_QThreadPool::releaseThread()
// releasing threads in children should not effect the parent // releasing threads in children should not effect the parent
{ {
QThreadPool threadpool2(threadpool); TestThreadPool threadpool2(threadpool);
threadpool2.setMaxThreadCount(limit); threadpool2.setMaxThreadCount(limit);
// reserve up to the limit // reserve up to the limit
@ -841,7 +857,7 @@ void tst_QThreadPool::start()
const int runs = 1000; const int runs = 1000;
count.storeRelaxed(0); count.storeRelaxed(0);
{ {
QThreadPool threadPool; TestThreadPool threadPool;
for (int i = 0; i< runs; ++i) { for (int i = 0; i< runs; ++i) {
threadPool.start(new CountingRunnable()); threadPool.start(new CountingRunnable());
} }
@ -868,13 +884,13 @@ void tst_QThreadPool::tryStart()
count.storeRelaxed(0); count.storeRelaxed(0);
WaitingTask task; WaitingTask task;
QThreadPool threadPool; TestThreadPool threadPool;
for (int i = 0; i < threadPool.maxThreadCount(); ++i) { for (int i = 0; i < threadPool.maxThreadCount(); ++i) {
threadPool.start(&task); threadPool.start(&task);
} }
QVERIFY(!threadPool.tryStart(&task)); QVERIFY(!threadPool.tryStart(&task));
task.semaphore.release(threadPool.maxThreadCount()); task.semaphore.release(threadPool.maxThreadCount());
threadPool.waitForDone(); WAIT_FOR_DONE(threadPool);
QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount()); QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount());
} }
@ -905,7 +921,7 @@ void tst_QThreadPool::tryStartPeakThreadCount()
}; };
CounterTask task; CounterTask task;
QThreadPool threadPool; TestThreadPool threadPool;
for (int i = 0; i < 4*QThread::idealThreadCount(); ++i) { for (int i = 0; i < 4*QThread::idealThreadCount(); ++i) {
if (threadPool.tryStart(&task) == false) if (threadPool.tryStart(&task) == false)
@ -934,7 +950,7 @@ void tst_QThreadPool::tryStartCount()
}; };
SleeperTask task; SleeperTask task;
QThreadPool threadPool; TestThreadPool threadPool;
const int runs = 5; const int runs = 5;
for (int i = 0; i < runs; ++i) { for (int i = 0; i < runs; ++i) {
@ -982,7 +998,7 @@ void tst_QThreadPool::priorityStart()
QSemaphore sem; QSemaphore sem;
QAtomicPointer<QRunnable> firstStarted; QAtomicPointer<QRunnable> firstStarted;
QRunnable *expected; QRunnable *expected;
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(1); // start only one thread at a time threadPool.setMaxThreadCount(1); // start only one thread at a time
// queue the holder first // queue the holder first
@ -994,7 +1010,7 @@ void tst_QThreadPool::priorityStart()
threadPool.start(expected = new Runner(firstStarted), 1); // priority 1 threadPool.start(expected = new Runner(firstStarted), 1); // priority 1
sem.release(); sem.release();
QVERIFY(threadPool.waitForDone()); WAIT_FOR_DONE(threadPool);
QCOMPARE(firstStarted.loadRelaxed(), expected); QCOMPARE(firstStarted.loadRelaxed(), expected);
} }
@ -1004,7 +1020,7 @@ void tst_QThreadPool::waitForDone()
total.start(); total.start();
pass.start(); pass.start();
QThreadPool threadPool; TestThreadPool threadPool;
while (total.elapsed() < 10000) { while (total.elapsed() < 10000) {
int runs; int runs;
count.storeRelaxed(runs = 0); count.storeRelaxed(runs = 0);
@ -1013,7 +1029,7 @@ void tst_QThreadPool::waitForDone()
threadPool.start(new CountingRunnable()); threadPool.start(new CountingRunnable());
++runs; ++runs;
} }
threadPool.waitForDone(); WAIT_FOR_DONE(threadPool);
QCOMPARE(count.loadRelaxed(), runs); QCOMPARE(count.loadRelaxed(), runs);
count.storeRelaxed(runs = 0); count.storeRelaxed(runs = 0);
@ -1023,7 +1039,7 @@ void tst_QThreadPool::waitForDone()
threadPool.start(new CountingRunnable()); threadPool.start(new CountingRunnable());
runs += 2; runs += 2;
} }
threadPool.waitForDone(); WAIT_FOR_DONE(threadPool);
QCOMPARE(count.loadRelaxed(), runs); QCOMPARE(count.loadRelaxed(), runs);
} }
} }
@ -1045,7 +1061,7 @@ void tst_QThreadPool::waitForDoneTimeout()
} }
}; };
QThreadPool threadPool; TestThreadPool threadPool;
mutex.lock(); mutex.lock();
threadPool.start(new BlockedTask(mutex)); threadPool.start(new BlockedTask(mutex));
@ -1069,7 +1085,7 @@ void tst_QThreadPool::clear()
} }
}; };
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(10); threadPool.setMaxThreadCount(10);
int runs = 2 * threadPool.maxThreadCount(); int runs = 2 * threadPool.maxThreadCount();
count.storeRelaxed(0); count.storeRelaxed(0);
@ -1078,7 +1094,7 @@ void tst_QThreadPool::clear()
} }
threadPool.clear(); threadPool.clear();
sem.release(threadPool.maxThreadCount()); sem.release(threadPool.maxThreadCount());
threadPool.waitForDone(); WAIT_FOR_DONE(threadPool);
QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount()); QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount());
} }
@ -1091,7 +1107,7 @@ void tst_QThreadPool::clearWithAutoDelete()
void run() override { QThread::sleep(30us); } void run() override { QThread::sleep(30us); }
}; };
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(4); threadPool.setMaxThreadCount(4);
const int loopCount = 20; const int loopCount = 20;
const int batchSize = 500; const int batchSize = 500;
@ -1104,7 +1120,6 @@ void tst_QThreadPool::clearWithAutoDelete()
threadPool.start(runnable); threadPool.start(runnable);
} }
} }
QVERIFY(threadPool.waitForDone());
} }
void tst_QThreadPool::tryTake() void tst_QThreadPool::tryTake()
@ -1144,7 +1159,7 @@ void tst_QThreadPool::tryTake()
Runs = MaxThreadCount * OverProvisioning Runs = MaxThreadCount * OverProvisioning
}; };
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(MaxThreadCount); threadPool.setMaxThreadCount(MaxThreadCount);
BlockingRunnable *runnables[Runs]; BlockingRunnable *runnables[Runs];
@ -1176,7 +1191,7 @@ void tst_QThreadPool::tryTake()
runnables[0]->dummy = 0; // valgrind will catch this if tryTake() is crazy enough to delete currently running jobs runnables[0]->dummy = 0; // valgrind will catch this if tryTake() is crazy enough to delete currently running jobs
QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - MaxThreadCount)); QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - MaxThreadCount));
sem.release(MaxThreadCount); sem.release(MaxThreadCount);
threadPool.waitForDone(); WAIT_FOR_DONE(threadPool);
QCOMPARE(runCounter.loadRelaxed(), int(MaxThreadCount)); QCOMPARE(runCounter.loadRelaxed(), int(MaxThreadCount));
QCOMPARE(count.loadRelaxed(), int(MaxThreadCount)); QCOMPARE(count.loadRelaxed(), int(MaxThreadCount));
QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - 1)); QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - 1));
@ -1193,7 +1208,7 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish()
int runs; int runs;
count.storeRelaxed(runs = 0); count.storeRelaxed(runs = 0);
{ {
QThreadPool threadPool; TestThreadPool threadPool;
pass.restart(); pass.restart();
while (pass.elapsed() < 100) { while (pass.elapsed() < 100) {
threadPool.start(new CountingRunnable()); threadPool.start(new CountingRunnable());
@ -1204,7 +1219,7 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish()
count.storeRelaxed(runs = 0); count.storeRelaxed(runs = 0);
{ {
QThreadPool threadPool; TestThreadPool threadPool;
pass.restart(); pass.restart();
while (pass.elapsed() < 100) { while (pass.elapsed() < 100) {
threadPool.start(new CountingRunnable()); threadPool.start(new CountingRunnable());
@ -1246,10 +1261,10 @@ void tst_QThreadPool::stackSize()
} }
}; };
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setStackSize(targetStackSize); threadPool.setStackSize(targetStackSize);
threadPool.start(new StackSizeChecker(&threadStackSize)); threadPool.start(new StackSizeChecker(&threadStackSize));
QVERIFY(threadPool.waitForDone(30000)); // 30s timeout WAIT_FOR_DONE(threadPool);
QCOMPARE(threadStackSize, targetStackSize); QCOMPARE(threadStackSize, targetStackSize);
} }
@ -1310,7 +1325,7 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() {
QSemaphore mainBarrier; QSemaphore mainBarrier;
QSemaphore taskBarrier; QSemaphore taskBarrier;
QThreadPool threadPool; TestThreadPool threadPool;
threadPool.setMaxThreadCount(1); threadPool.setMaxThreadCount(1);
Task task1(&mainBarrier, &taskBarrier); Task task1(&mainBarrier, &taskBarrier);
@ -1341,7 +1356,7 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() {
taskBarrier.release(1); taskBarrier.release(1);
threadPool.waitForDone(); WAIT_FOR_DONE(threadPool);
QCOMPARE(threadPool.activeThreadCount(), 0); QCOMPARE(threadPool.activeThreadCount(), 0);
} }
@ -1374,7 +1389,7 @@ void tst_QThreadPool::waitForDoneAfterTake()
// Blocks the tasks from completing their run function // Blocks the tasks from completing their run function
QSemaphore threadBarrier; QSemaphore threadBarrier;
QThreadPool manager; TestThreadPool manager;
manager.setMaxThreadCount(threadCount); manager.setMaxThreadCount(threadCount);
// Fill all the threads with runnables that wait for the threadBarrier // Fill all the threads with runnables that wait for the threadBarrier
@ -1405,12 +1420,6 @@ void tst_QThreadPool::waitForDoneAfterTake()
// Release runnables that are waiting and expect all runnables to complete // Release runnables that are waiting and expect all runnables to complete
threadBarrier.release(threadCount); 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.");
} }
/* /*
@ -1420,7 +1429,7 @@ void tst_QThreadPool::waitForDoneAfterTake()
*/ */
void tst_QThreadPool::threadReuse() void tst_QThreadPool::threadReuse()
{ {
QThreadPool manager; TestThreadPool manager;
manager.setExpiryTimeout(-1); manager.setExpiryTimeout(-1);
manager.setMaxThreadCount(1); manager.setMaxThreadCount(1);