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:
parent
7c2a418857
commit
dacf9961da
@ -180,6 +180,7 @@ bool QThreadPoolPrivate::tryStart(QRunnable *task)
|
|||||||
// recycle an available thread
|
// recycle an available thread
|
||||||
--waitingThreads;
|
--waitingThreads;
|
||||||
enqueueTask(task);
|
enqueueTask(task);
|
||||||
|
runnableReady.wakeOne();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +219,6 @@ void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority)
|
|||||||
if (it != begin && priority > (*(it - 1)).second)
|
if (it != begin && priority > (*(it - 1)).second)
|
||||||
it = std::upper_bound(begin, --it, priority);
|
it = std::upper_bound(begin, --it, priority);
|
||||||
queue.insert(it - begin, qMakePair(runnable, priority));
|
queue.insert(it - begin, qMakePair(runnable, priority));
|
||||||
runnableReady.wakeOne();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int QThreadPoolPrivate::activeThreadCount() const
|
int QThreadPoolPrivate::activeThreadCount() const
|
||||||
@ -456,8 +456,14 @@ void QThreadPool::start(QRunnable *runnable, int priority)
|
|||||||
|
|
||||||
Q_D(QThreadPool);
|
Q_D(QThreadPool);
|
||||||
QMutexLocker locker(&d->mutex);
|
QMutexLocker locker(&d->mutex);
|
||||||
if (!d->tryStart(runnable))
|
if (!d->tryStart(runnable)) {
|
||||||
d->enqueueTask(runnable, priority);
|
d->enqueueTask(runnable, priority);
|
||||||
|
|
||||||
|
if (d->waitingThreads > 0) {
|
||||||
|
--d->waitingThreads;
|
||||||
|
d->runnableReady.wakeOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -90,6 +90,7 @@ private slots:
|
|||||||
void reserveThread();
|
void reserveThread();
|
||||||
void releaseThread_data();
|
void releaseThread_data();
|
||||||
void releaseThread();
|
void releaseThread();
|
||||||
|
void reserveAndStart();
|
||||||
void start();
|
void start();
|
||||||
void tryStart();
|
void tryStart();
|
||||||
void tryStartPeakThreadCount();
|
void tryStartPeakThreadCount();
|
||||||
@ -630,6 +631,61 @@ void tst_QThreadPool::releaseThread()
|
|||||||
threadpool->setMaxThreadCount(savedLimit);
|
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;
|
QAtomicInt count;
|
||||||
class CountingRunnable : public QRunnable
|
class CountingRunnable : public QRunnable
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user