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 <thiago.macieira@intel.com>
This commit is contained in:
David Faure 2013-06-21 10:32:17 +02:00 committed by The Qt Project
parent 7c2a418857
commit dacf9961da
2 changed files with 64 additions and 2 deletions

View File

@ -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();
}
}
}
/*!

View File

@ -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
{