Add QThreadPool autotest to detect stale threads after tryTake
This test makes sure that we do not introduce a regression where the threads exited the inner loop over the queue before the queue was empty. This was triggered by calling tryTake at least maxThreadCount times, which left the same number of null pointers in the queue and caused the inner loop to exit too soon for all the threads. Change-Id: I3a9d800149b88d09510ddc424667670b60f06a33 Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
parent
f6c9f03128
commit
ce08318a46
@ -94,6 +94,7 @@ private slots:
|
|||||||
void destroyingWaitsForTasksToFinish();
|
void destroyingWaitsForTasksToFinish();
|
||||||
void stressTest();
|
void stressTest();
|
||||||
void takeAllAndIncreaseMaxThreadCount();
|
void takeAllAndIncreaseMaxThreadCount();
|
||||||
|
void waitForDoneAfterTake();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMutex m_functionTestMutex;
|
QMutex m_functionTestMutex;
|
||||||
@ -1263,5 +1264,72 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() {
|
|||||||
delete task3;
|
delete task3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QThreadPool::waitForDoneAfterTake()
|
||||||
|
{
|
||||||
|
class Task : public QRunnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Task(QSemaphore *mainBarrier, QSemaphore *threadBarrier)
|
||||||
|
: m_mainBarrier(mainBarrier)
|
||||||
|
, m_threadBarrier(threadBarrier)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
m_mainBarrier->release();
|
||||||
|
m_threadBarrier->acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSemaphore *m_mainBarrier = nullptr;
|
||||||
|
QSemaphore *m_threadBarrier = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
int threadCount = 4;
|
||||||
|
|
||||||
|
// Blocks the main thread from releasing the threadBarrier before all run() functions have started
|
||||||
|
QSemaphore mainBarrier;
|
||||||
|
// Blocks the tasks from completing their run function
|
||||||
|
QSemaphore threadBarrier;
|
||||||
|
|
||||||
|
QThreadPool manager;
|
||||||
|
manager.setMaxThreadCount(threadCount);
|
||||||
|
|
||||||
|
// Fill all the threads with runnables that wait for the threadBarrier
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
auto *task = new Task(&mainBarrier, &threadBarrier);
|
||||||
|
manager.start(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY(manager.activeThreadCount() == manager.maxThreadCount());
|
||||||
|
|
||||||
|
// Add runnables that are immediately removed from the pool queue.
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add another runnable that will not be removed
|
||||||
|
manager.start(createTask(emptyFunct));
|
||||||
|
|
||||||
|
// Wait for the first runnables to start
|
||||||
|
mainBarrier.acquire(threadCount);
|
||||||
|
|
||||||
|
QVERIFY(mainBarrier.available() == 0);
|
||||||
|
QVERIFY(threadBarrier.available() == 0);
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QThreadPool);
|
QTEST_MAIN(tst_QThreadPool);
|
||||||
#include "tst_qthreadpool.moc"
|
#include "tst_qthreadpool.moc"
|
||||||
|
Loading…
Reference in New Issue
Block a user