diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp index f29f950a1f6..dfc905e8283 100644 --- a/src/corelib/thread/qfutureinterface.cpp +++ b/src/corelib/thread/qfutureinterface.cpp @@ -858,12 +858,25 @@ void QFutureInterfaceBase::setContinuation(std::functioncontinuationMutex); + d->continuation = nullptr; +} + void QFutureInterfaceBase::runContinuation() const { QMutexLocker lock(&d->continuationMutex); if (d->continuation) { + auto fn = std::exchange(d->continuation, nullptr); lock.unlock(); - d->continuation(*this); + fn(*this); } } diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h index 3130c914203..205b2de9f95 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -212,10 +212,14 @@ private: friend class QtPrivate::FailureHandler; #endif + template + friend class QPromise; + protected: void setContinuation(std::function func); void setContinuation(std::function func, QFutureInterfaceBasePrivate *continuationFutureData); + void cleanContinuation(); void runContinuation() const; void setLaunchAsync(bool value); diff --git a/src/corelib/thread/qpromise.h b/src/corelib/thread/qpromise.h index a970f40d126..f62a2a54e31 100644 --- a/src/corelib/thread/qpromise.h +++ b/src/corelib/thread/qpromise.h @@ -66,12 +66,16 @@ public: { const int state = d.loadState(); // If QFutureInterface has no state, there is nothing to be done - if (state == static_cast(QFutureInterfaceBase::State::NoState)) + if (state == static_cast(QFutureInterfaceBase::State::NoState)) { + d.cleanContinuation(); return; + } // Otherwise, if computation is not finished at this point, cancel // potential waits - if (!(state & QFutureInterfaceBase::State::Finished)) + if (!(state & QFutureInterfaceBase::State::Finished)) { d.cancelAndFinish(); // cancel and finalize the state + d.cleanContinuation(); + } } // Core QPromise APIs diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 7b272cf5a3f..943ea2ea34c 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -194,6 +194,8 @@ private slots: void whenAnyDifferentTypesWithCanceled(); void whenAnyDifferentTypesWithFailed(); + void continuationsDontLeak(); + private: using size_type = std::vector::size_type; @@ -4355,5 +4357,65 @@ void tst_QFuture::whenAnyDifferentTypesWithFailed() #endif } +struct InstanceCounter +{ + InstanceCounter() { ++count; } + InstanceCounter(const InstanceCounter &) { ++count; } + ~InstanceCounter() { --count; } + static int count; +}; +int InstanceCounter::count = 0; + +void tst_QFuture::continuationsDontLeak() +{ + { + // QFuture isn't started and isn't finished (has no state) + QPromise promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { continuationIsRun = true; }); + + promise.addResult(InstanceCounter {}); + + QVERIFY(!continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QFuture is started, but not finished + QPromise promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { continuationIsRun = true; }); + + promise.start(); + promise.addResult(InstanceCounter {}); + + QVERIFY(!continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QFuture is started and finished, the continuation is run + QPromise promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { + QVERIFY(future.isFinished()); + continuationIsRun = true; + }); + + promise.start(); + promise.addResult(InstanceCounter {}); + promise.finish(); + + QVERIFY(continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); +} + QTEST_MAIN(tst_QFuture) #include "tst_qfuture.moc"