Fix race condition in QThreadPool::clear

Since we drop the lock while deleting threads, we need to handle
the queue possibly being accessed and changed by the pool threads
while clear() is running.

Pick-to: 5.15
Fixes: QTBUG-87092
Change-Id: I7611edab90520454278502a58621e299f9cd1f6e
Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2020-10-01 09:56:16 +02:00
parent 4d943225eb
commit fe36d47b37
2 changed files with 29 additions and 3 deletions

View File

@ -325,7 +325,8 @@ bool QThreadPoolPrivate::waitForDone(int msecs)
void QThreadPoolPrivate::clear()
{
QMutexLocker locker(&mutex);
for (QueuePage *page : qAsConst(queue)) {
while (!queue.isEmpty()) {
auto *page = queue.takeLast();
while (!page->isFinished()) {
QRunnable *r = page->pop();
if (r && r->autoDelete()) {
@ -335,9 +336,8 @@ void QThreadPoolPrivate::clear()
locker.relock();
}
}
delete page;
}
qDeleteAll(queue);
queue.clear();
}
/*!

View File

@ -93,6 +93,7 @@ private slots:
void priorityStart();
void waitForDone();
void clear();
void clearWithAutoDelete();
void tryTake();
void waitForDoneTimeout();
void destroyingWaitsForTasksToFinish();
@ -974,6 +975,31 @@ void tst_QThreadPool::clear()
QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount());
}
void tst_QThreadPool::clearWithAutoDelete()
{
class MyRunnable : public QRunnable
{
public:
MyRunnable() {}
void run() override { QThread::usleep(30); }
};
QThreadPool 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);
}
}
QVERIFY(threadPool.waitForDone());
}
void tst_QThreadPool::tryTake()
{
QSemaphore sem(0);