diff options
-rw-r--r-- | src/corelib/thread/qfutureinterface.cpp | 15 | ||||
-rw-r--r-- | src/corelib/thread/qfutureinterface.h | 4 | ||||
-rw-r--r-- | src/corelib/thread/qpromise.h | 8 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 62 |
4 files changed, 86 insertions, 3 deletions
diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp index f29f950a1f..dfc905e828 100644 --- a/src/corelib/thread/qfutureinterface.cpp +++ b/src/corelib/thread/qfutureinterface.cpp @@ -858,12 +858,25 @@ void QFutureInterfaceBase::setContinuation(std::function<void(const QFutureInter } } +void QFutureInterfaceBase::cleanContinuation() +{ + if (!d) + return; + + // This is called when the associated QPromise is being destroyed. + // Clear the continuation, to make sure it doesn't keep any ref-counted + // copies of this, so that the allocated memory can be freed. + QMutexLocker lock(&d->continuationMutex); + 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 3130c91420..205b2de9f9 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -212,10 +212,14 @@ private: friend class QtPrivate::FailureHandler; #endif + template<class T> + friend class QPromise; + protected: void setContinuation(std::function<void(const QFutureInterfaceBase &)> func); void setContinuation(std::function<void(const QFutureInterfaceBase &)> 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 a970f40d12..f62a2a54e3 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<int>(QFutureInterfaceBase::State::NoState)) + if (state == static_cast<int>(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 7b272cf5a3..943ea2ea34 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<int>::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<InstanceCounter> 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<InstanceCounter> 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<InstanceCounter> 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" |