summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/thread/qfutureinterface.cpp15
-rw-r--r--src/corelib/thread/qfutureinterface.h4
-rw-r--r--src/corelib/thread/qpromise.h8
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp62
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"