Fix QRunnable::ref use in QThreadPool

The reference counter could only ever be -1, 0 or 1,
as an autoDeleted QRunnable can only be in a single
thread pool.

This commits keeps the reference counting for now,
but asserts sanity, simplifies locking and fixes a
leak.

Pick-To: 5.15
Fixes: QTBUG-20251
Fixes: QTBUG-65486
Change-Id: I4de44e9a4e3710225971d1eab8f2e332513f75ad
Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2020-01-28 15:55:07 +01:00
parent 9b4b406142
commit 891b60bbc8
2 changed files with 43 additions and 25 deletions

View File

@ -47,7 +47,7 @@ QT_BEGIN_NAMESPACE
class Q_CORE_EXPORT QRunnable class Q_CORE_EXPORT QRunnable
{ {
int ref; int ref; // Qt6: Make this a bool, or make autoDelete() virtual.
friend class QThreadPool; friend class QThreadPool;
friend class QThreadPoolPrivate; friend class QThreadPoolPrivate;

View File

@ -88,7 +88,8 @@ void QThreadPoolThread::run()
do { do {
if (r) { if (r) {
const bool autoDelete = r->autoDelete(); const bool del = r->autoDelete();
Q_ASSERT(!del || r->ref == 1);
// run the task // run the task
@ -106,10 +107,10 @@ void QThreadPoolThread::run()
throw; throw;
} }
#endif #endif
locker.relock();
if (autoDelete && !--r->ref) if (del)
delete r; delete r;
locker.relock();
} }
// if too many threads are active, expire this thread // if too many threads are active, expire this thread
@ -193,8 +194,6 @@ bool QThreadPoolPrivate::tryStart(QRunnable *task)
++activeThreads; ++activeThreads;
if (task->autoDelete())
++task->ref;
thread->runnable = task; thread->runnable = task;
thread->start(); thread->start();
return true; return true;
@ -213,9 +212,6 @@ inline bool comparePriority(int priority, const QueuePage *p)
void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority) void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority)
{ {
Q_ASSERT(runnable != nullptr); Q_ASSERT(runnable != nullptr);
if (runnable->autoDelete())
++runnable->ref;
for (QueuePage *page : qAsConst(queue)) { for (QueuePage *page : qAsConst(queue)) {
if (page->priority() == priority && !page->isFull()) { if (page->priority() == priority && !page->isFull()) {
page->push(runnable); page->push(runnable);
@ -269,8 +265,6 @@ void QThreadPoolPrivate::startThread(QRunnable *runnable)
allThreads.insert(thread.data()); allThreads.insert(thread.data());
++activeThreads; ++activeThreads;
if (runnable->autoDelete())
++runnable->ref;
thread->runnable = runnable; thread->runnable = runnable;
thread.take()->start(); thread.take()->start();
} }
@ -334,8 +328,12 @@ void QThreadPoolPrivate::clear()
for (QueuePage *page : qAsConst(queue)) { for (QueuePage *page : qAsConst(queue)) {
while (!page->isFinished()) { while (!page->isFinished()) {
QRunnable *r = page->pop(); QRunnable *r = page->pop();
if (r && r->autoDelete() && !--r->ref) if (r && r->autoDelete()) {
Q_ASSERT(r->ref == 1);
locker.unlock();
delete r; delete r;
locker.relock();
}
} }
} }
qDeleteAll(queue); qDeleteAll(queue);
@ -365,19 +363,19 @@ bool QThreadPool::tryTake(QRunnable *runnable)
if (runnable == nullptr) if (runnable == nullptr)
return false; return false;
{
QMutexLocker locker(&d->mutex);
for (QueuePage *page : qAsConst(d->queue)) { QMutexLocker locker(&d->mutex);
if (page->tryTake(runnable)) { for (QueuePage *page : qAsConst(d->queue)) {
if (page->isFinished()) { if (page->tryTake(runnable)) {
d->queue.removeOne(page); if (page->isFinished()) {
delete page; d->queue.removeOne(page);
} delete page;
if (runnable->autoDelete())
--runnable->ref; // undo ++ref in start()
return true;
} }
if (runnable->autoDelete()) {
Q_ASSERT(runnable->ref == 1);
--runnable->ref; // undo ++ref in start()
}
return true;
} }
} }
@ -395,11 +393,12 @@ void QThreadPoolPrivate::stealAndRunRunnable(QRunnable *runnable)
Q_Q(QThreadPool); Q_Q(QThreadPool);
if (!q->tryTake(runnable)) if (!q->tryTake(runnable))
return; return;
const bool del = runnable->autoDelete() && !runnable->ref; // tryTake already deref'ed const bool del = runnable->autoDelete();
runnable->run(); runnable->run();
if (del) { if (del) {
Q_ASSERT(runnable->ref == 0); // tryTake already deref'ed
delete runnable; delete runnable;
} }
} }
@ -503,6 +502,11 @@ void QThreadPool::start(QRunnable *runnable, int priority)
Q_D(QThreadPool); Q_D(QThreadPool);
QMutexLocker locker(&d->mutex); QMutexLocker locker(&d->mutex);
if (runnable->autoDelete()) {
Q_ASSERT(runnable->ref == 0);
++runnable->ref;
}
if (!d->tryStart(runnable)) { if (!d->tryStart(runnable)) {
d->enqueueTask(runnable, priority); d->enqueueTask(runnable, priority);
@ -548,9 +552,23 @@ bool QThreadPool::tryStart(QRunnable *runnable)
if (!runnable) if (!runnable)
return false; return false;
if (runnable->autoDelete()) {
Q_ASSERT(runnable->ref == 0);
++runnable->ref;
}
Q_D(QThreadPool); Q_D(QThreadPool);
QMutexLocker locker(&d->mutex); QMutexLocker locker(&d->mutex);
return d->tryStart(runnable); if (d->tryStart(runnable))
return true;
// Undo the reference above as we did not start the runnable and
// take over ownership.
if (runnable->autoDelete()) {
--runnable->ref;
Q_ASSERT(runnable->ref == 0);
}
return false;
} }
/*! /*!