From dacf9961da86751a59da0e84bc943fe0d1c8d95b Mon Sep 17 00:00:00 2001 From: David Faure Date: Fri, 21 Jun 2013 10:32:17 +0200 Subject: [PATCH] QThreadPool: fix counting of waiting threads QTBUG-21051 has a testcase where activeThreadCount() could actually end up at -1 (converted to an autotest in this commit). The reason was: start() calls tryStart() which returns false due to too many active threads (reserveThread() causes this), so it calls enqueueTask() - which actually wakes up the waiting thread, but it didn't decrement the number of waiting threads. Note that tryStart() is "if I can grab a waiting thread, enqueue task and wake it" while start(), in case tryStart() fails, wants to "enqueue, and then if I can grab a waiting thread, wake it". This is why enqueue shouldn't wake; waking must happen only if we can grab a thread (d->waitingThreads > 0). Task-number: QTBUG-21051 Change-Id: I3d98337103031c9bdf0bf365295f245be0c66aa7 Reviewed-by: Thiago Macieira --- src/corelib/thread/qthreadpool.cpp | 10 +++- .../thread/qthreadpool/tst_qthreadpool.cpp | 56 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/corelib/thread/qthreadpool.cpp b/src/corelib/thread/qthreadpool.cpp index 31c3f5a256b..9ef40a52097 100644 --- a/src/corelib/thread/qthreadpool.cpp +++ b/src/corelib/thread/qthreadpool.cpp @@ -180,6 +180,7 @@ bool QThreadPoolPrivate::tryStart(QRunnable *task) // recycle an available thread --waitingThreads; enqueueTask(task); + runnableReady.wakeOne(); return true; } @@ -218,7 +219,6 @@ void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority) if (it != begin && priority > (*(it - 1)).second) it = std::upper_bound(begin, --it, priority); queue.insert(it - begin, qMakePair(runnable, priority)); - runnableReady.wakeOne(); } int QThreadPoolPrivate::activeThreadCount() const @@ -456,8 +456,14 @@ void QThreadPool::start(QRunnable *runnable, int priority) Q_D(QThreadPool); QMutexLocker locker(&d->mutex); - if (!d->tryStart(runnable)) + if (!d->tryStart(runnable)) { d->enqueueTask(runnable, priority); + + if (d->waitingThreads > 0) { + --d->waitingThreads; + d->runnableReady.wakeOne(); + } + } } /*! diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 10a59ac3017..cdf240d7577 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -90,6 +90,7 @@ private slots: void reserveThread(); void releaseThread_data(); void releaseThread(); + void reserveAndStart(); void start(); void tryStart(); void tryStartPeakThreadCount(); @@ -630,6 +631,61 @@ void tst_QThreadPool::releaseThread() threadpool->setMaxThreadCount(savedLimit); } +void tst_QThreadPool::reserveAndStart() // QTBUG-21051 +{ + class WaitingTask : public QRunnable + { + public: + QAtomicInt count; + QSemaphore waitForStarted; + + WaitingTask() { setAutoDelete(false); } + + void run() + { + count.ref(); + waitForStarted.release(); + } + }; + + // Set up + QThreadPool *threadpool = QThreadPool::globalInstance(); + int savedLimit = threadpool->maxThreadCount(); + threadpool->setMaxThreadCount(1); + QCOMPARE(threadpool->activeThreadCount(), 0); + + // reserve + threadpool->reserveThread(); + QCOMPARE(threadpool->activeThreadCount(), 1); + + // start a task, to get a running thread + WaitingTask *task = new WaitingTask; + threadpool->start(task); + QCOMPARE(threadpool->activeThreadCount(), 2); + task->waitForStarted.acquire(); + QTRY_COMPARE(task->count.load(), 1); + QTRY_COMPARE(threadpool->activeThreadCount(), 1); + + // now the thread is waiting, but tryStart() will fail since activeThreadCount() >= maxThreadCount() + QVERIFY(!threadpool->tryStart(task)); + QTRY_COMPARE(threadpool->activeThreadCount(), 1); + + // 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(); + QTRY_COMPARE(task->count.load(), 2); + QTRY_COMPARE(threadpool->activeThreadCount(), 1); + + threadpool->releaseThread(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); + + delete task; + + threadpool->setMaxThreadCount(savedLimit); +} + QAtomicInt count; class CountingRunnable : public QRunnable {