summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2022-01-17 14:45:32 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-01-21 03:05:50 +0000
commit22cb8d9527dd1c404732df3cd36b299a675653f7 (patch)
tree306db7852febecec7af901c7022099c93019b544 /tests
parent728f4c1f4e396880dbaf269a3f42a6be9760b48e (diff)
Fix memory leaks when capturing a QFuture in its continuation
Capturing a QFuture in the continuations attached to it results in memory leaks. QFuture's ref-counted data can only be deleted when the last copy referencing the data gets deleted. The saved continuation that keeps a copy of the future (as in case of the lambda capture) will prevent the data from being deleted. So we need to manually clean the continuation after it is run. But this doesn't solve the problem if the continuation isn't run. In that case, clean the continuation in the destructor of the associated QPromise. To avoid similar leaks, internally we should always create futures via QPromise, instead of the ref-counted QFutureInterface, so that the continuation is always cleaned in the destructor. Currently QFuture continuations and QtFuture::when* methods use QFutureInterface directly, which will be fixed by the follow-up commits. Fixes: QTBUG-99534 Change-Id: Ic13e7dffd8cb25bd6b87e5416fe4d1a97af74c9b Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> (cherry picked from commit a8794503ebdefec53a7a42479c477d04f0efa067) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp62
1 files changed, 62 insertions, 0 deletions
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"