Skip to content

Commit

Permalink
Fix QRunnable::ref use in QThreadPool
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Allan Sandfeld Jensen committed May 7, 2020
1 parent 9b4b406 commit 891b60b
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/corelib/thread/qrunnable.h
Expand Up @@ -47,7 +47,7 @@ QT_BEGIN_NAMESPACE

class Q_CORE_EXPORT QRunnable
{
int ref;
int ref; // Qt6: Make this a bool, or make autoDelete() virtual.

friend class QThreadPool;
friend class QThreadPoolPrivate;
Expand Down
68 changes: 43 additions & 25 deletions src/corelib/thread/qthreadpool.cpp
Expand Up @@ -88,7 +88,8 @@ void QThreadPoolThread::run()

do {
if (r) {
const bool autoDelete = r->autoDelete();
const bool del = r->autoDelete();
Q_ASSERT(!del || r->ref == 1);


// run the task
Expand All @@ -106,10 +107,10 @@ void QThreadPoolThread::run()
throw;
}
#endif
locker.relock();

if (autoDelete && !--r->ref)
if (del)
delete r;
locker.relock();
}

// if too many threads are active, expire this thread
Expand Down Expand Up @@ -193,8 +194,6 @@ bool QThreadPoolPrivate::tryStart(QRunnable *task)

++activeThreads;

if (task->autoDelete())
++task->ref;
thread->runnable = task;
thread->start();
return true;
Expand All @@ -213,9 +212,6 @@ inline bool comparePriority(int priority, const QueuePage *p)
void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority)
{
Q_ASSERT(runnable != nullptr);
if (runnable->autoDelete())
++runnable->ref;

for (QueuePage *page : qAsConst(queue)) {
if (page->priority() == priority && !page->isFull()) {
page->push(runnable);
Expand Down Expand Up @@ -269,8 +265,6 @@ void QThreadPoolPrivate::startThread(QRunnable *runnable)
allThreads.insert(thread.data());
++activeThreads;

if (runnable->autoDelete())
++runnable->ref;
thread->runnable = runnable;
thread.take()->start();
}
Expand Down Expand Up @@ -334,8 +328,12 @@ void QThreadPoolPrivate::clear()
for (QueuePage *page : qAsConst(queue)) {
while (!page->isFinished()) {
QRunnable *r = page->pop();
if (r && r->autoDelete() && !--r->ref)
if (r && r->autoDelete()) {
Q_ASSERT(r->ref == 1);
locker.unlock();
delete r;
locker.relock();
}
}
}
qDeleteAll(queue);
Expand Down Expand Up @@ -365,19 +363,19 @@ bool QThreadPool::tryTake(QRunnable *runnable)

if (runnable == nullptr)
return false;
{
QMutexLocker locker(&d->mutex);

for (QueuePage *page : qAsConst(d->queue)) {
if (page->tryTake(runnable)) {
if (page->isFinished()) {
d->queue.removeOne(page);
delete page;
}
if (runnable->autoDelete())
--runnable->ref; // undo ++ref in start()
return true;

QMutexLocker locker(&d->mutex);
for (QueuePage *page : qAsConst(d->queue)) {
if (page->tryTake(runnable)) {
if (page->isFinished()) {
d->queue.removeOne(page);
delete page;
}
if (runnable->autoDelete()) {
Q_ASSERT(runnable->ref == 1);
--runnable->ref; // undo ++ref in start()
}
return true;
}
}

Expand All @@ -395,11 +393,12 @@ void QThreadPoolPrivate::stealAndRunRunnable(QRunnable *runnable)
Q_Q(QThreadPool);
if (!q->tryTake(runnable))
return;
const bool del = runnable->autoDelete() && !runnable->ref; // tryTake already deref'ed
const bool del = runnable->autoDelete();

runnable->run();

if (del) {
Q_ASSERT(runnable->ref == 0); // tryTake already deref'ed
delete runnable;
}
}
Expand Down Expand Up @@ -503,6 +502,11 @@ void QThreadPool::start(QRunnable *runnable, int priority)

Q_D(QThreadPool);
QMutexLocker locker(&d->mutex);
if (runnable->autoDelete()) {
Q_ASSERT(runnable->ref == 0);
++runnable->ref;
}

if (!d->tryStart(runnable)) {
d->enqueueTask(runnable, priority);

Expand Down Expand Up @@ -548,9 +552,23 @@ bool QThreadPool::tryStart(QRunnable *runnable)
if (!runnable)
return false;

if (runnable->autoDelete()) {
Q_ASSERT(runnable->ref == 0);
++runnable->ref;
}

Q_D(QThreadPool);
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;
}

/*!
Expand Down

0 comments on commit 891b60b

Please sign in to comment.