From 614847eae99bd4b6ce375f9f5572acfb3b513307 Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Tue, 18 Jan 2022 13:07:43 +0100 Subject: [PATCH] Use QPromise when creating continuations to avoid memory leaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continuations were using QFutureInterface to create and return the associated future to the user. Attaching a continuation to the returned future could cause memory leaks (described in an earlier commit). Use a QPromise when saving the continuation, to make sure that the attached continuation is cleaned in the destructor of the associated QPromise, so that it doesn't keep any ref-counted copies to the shared data, thus preventing it from being deleted. Task-number: QTBUG-99534 Pick-to: 6.3 6.2 Change-Id: I52d5501292095d41d1e060b7dd140c8e5d01335c Reviewed-by: Andrei Golubev Reviewed-by: MÃ¥rten Nordheim Reviewed-by: Edward Welbourne --- src/corelib/thread/qfuture_impl.h | 238 ++++++++---------- .../thread/qfuture/tst_bench_qfuture.cpp | 1 + 2 files changed, 103 insertions(+), 136 deletions(-) diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 869a791b15..112ac21f9e 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -332,9 +332,8 @@ class Continuation { public: template - Continuation(F &&func, const QFuture &f, - const QFutureInterface &p) - : promise(p), parentFuture(f), function(std::forward(func)) + Continuation(F &&func, const QFuture &f, QPromise &&p) + : promise(std::move(p)), parentFuture(f), function(std::forward(func)) { } virtual ~Continuation() = default; @@ -342,15 +341,15 @@ public: bool execute(); template - static void create(F &&func, QFuture *f, QFutureInterface &p, + static void create(F &&func, QFuture *f, QFutureInterface &fi, QtFuture::Launch policy); template - static void create(F &&func, QFuture *f, QFutureInterface &p, + static void create(F &&func, QFuture *f, QFutureInterface &fi, QThreadPool *pool); template - static void create(F &&func, QFuture *f, QFutureInterface &p, + static void create(F &&func, QFuture *f, QFutureInterface &fi, QObject *context); private: @@ -367,7 +366,7 @@ protected: void runFunction(); protected: - QFutureInterface promise; + QPromise promise; QFuture parentFuture; Function function; }; @@ -377,9 +376,9 @@ class SyncContinuation final : public Continuation - SyncContinuation(F &&func, const QFuture &f, - const QFutureInterface &p) - : Continuation(std::forward(func), f, p) + SyncContinuation(F &&func, const QFuture &f, QPromise &&p) + : Continuation(std::forward(func), f, + std::move(p)) { } @@ -395,12 +394,12 @@ class AsyncContinuation final : public QRunnable, { public: template - AsyncContinuation(F &&func, const QFuture &f, - const QFutureInterface &p, QThreadPool *pool = nullptr) - : Continuation(std::forward(func), f, p), + AsyncContinuation(F &&func, const QFuture &f, QPromise &&p, + QThreadPool *pool = nullptr) + : Continuation(std::forward(func), f, + std::move(p)), threadPool(pool) { - this->promise.setRunnable(this); } ~AsyncContinuation() override = default; @@ -429,15 +428,15 @@ class FailureHandler public: template static void create(F &&function, QFuture *future, - const QFutureInterface &promise); + const QFutureInterface &fi); template - static void create(F &&function, QFuture *future, - QFutureInterface &promise, QObject *context); + static void create(F &&function, QFuture *future, QFutureInterface &fi, + QObject *context); template - FailureHandler(F &&func, const QFuture &f, const QFutureInterface &p) - : promise(p), parentFuture(f), handler(std::forward(func)) + FailureHandler(F &&func, const QFuture &f, QPromise &&p) + : promise(std::move(p)), parentFuture(f), handler(std::forward(func)) { } @@ -450,7 +449,7 @@ private: void handleAllExceptions(); private: - QFutureInterface promise; + QPromise promise; QFuture parentFuture; Function handler; }; @@ -460,7 +459,7 @@ private: template void Continuation::runFunction() { - promise.reportStarted(); + promise.start(); Q_ASSERT(parentFuture.isFinished()); @@ -497,10 +496,10 @@ void Continuation::runFunction() } #ifndef QT_NO_EXCEPTIONS } catch (...) { - promise.reportException(std::current_exception()); + promise.setException(std::current_exception()); } #endif - promise.reportFinished(); + promise.finish(); } template @@ -516,17 +515,17 @@ bool Continuation::execute() // the user may want to catch the exception inside the continuation, to not // interrupt the continuation chain, so don't report anything yet. if constexpr (!std::is_invocable_v, QFuture>) { - promise.reportStarted(); - promise.reportException(parentFuture.d.exceptionStore().exception()); - promise.reportFinished(); + promise.start(); + promise.setException(parentFuture.d.exceptionStore().exception()); + promise.finish(); return false; } } else #endif { - promise.reportStarted(); - promise.reportCanceled(); - promise.reportFinished(); + promise.start(); + promise.future().cancel(); + promise.finish(); return false; } } @@ -550,7 +549,7 @@ template template void Continuation::create(F &&func, QFuture *f, - QFutureInterface &p, + QFutureInterface &fi, QtFuture::Launch policy) { Q_ASSERT(f); @@ -564,22 +563,24 @@ void Continuation::create(F &&func, // If the parent future was using a custom thread pool, inherit it as well. if (launchAsync && f->d.threadPool()) { pool = f->d.threadPool(); - p.setThreadPool(pool); + fi.setThreadPool(pool); } } - p.setLaunchAsync(launchAsync); + fi.setLaunchAsync(launchAsync); - auto continuation = [func = std::forward(func), p, pool, + auto continuation = [func = std::forward(func), fi, promise = QPromise(fi), pool, launchAsync](const QFutureInterfaceBase &parentData) mutable { const auto parent = QFutureInterface(parentData).future(); Continuation *continuationJob = nullptr; if (launchAsync) { - continuationJob = new AsyncContinuation( - std::forward(func), parent, p, pool); + auto asyncJob = new AsyncContinuation( + std::forward(func), parent, std::move(promise), pool); + fi.setRunnable(asyncJob); + continuationJob = asyncJob; } else { continuationJob = new SyncContinuation( - std::forward(func), parent, p); + std::forward(func), parent, std::move(promise)); } bool isLaunched = continuationJob->execute(); @@ -591,29 +592,26 @@ void Continuation::create(F &&func, continuationJob = nullptr; } }; - if constexpr (!std::is_copy_constructible_v) - f->d.setContinuation(ContinuationWrapper(std::move(continuation)), p.d); - else - f->d.setContinuation(std::move(continuation), p.d); + f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); } template template void Continuation::create(F &&func, QFuture *f, - QFutureInterface &p, + QFutureInterface &fi, QThreadPool *pool) { Q_ASSERT(f); - p.setLaunchAsync(true); - p.setThreadPool(pool); + fi.setLaunchAsync(true); + fi.setThreadPool(pool); - auto continuation = [func = std::forward(func), p, + auto continuation = [func = std::forward(func), promise = QPromise(fi), pool](const QFutureInterfaceBase &parentData) mutable { const auto parent = QFutureInterface(parentData).future(); auto continuationJob = new AsyncContinuation( - std::forward(func), parent, p, pool); + std::forward(func), parent, std::move(promise), pool); bool isLaunched = continuationJob->execute(); // If continuation is successfully launched, AsyncContinuation will be deleted // by the QThreadPool which has started it. @@ -622,37 +620,32 @@ void Continuation::create(F &&func, continuationJob = nullptr; } }; - - if constexpr (!std::is_copy_constructible_v) - f->d.setContinuation(ContinuationWrapper(std::move(continuation)), p.d); - else - f->d.setContinuation(std::move(continuation), p.d); + f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); } template template void Continuation::create(F &&func, QFuture *f, - QFutureInterface &p, + QFutureInterface &fi, QObject *context) { Q_ASSERT(f); - auto continuation = [func = std::forward(func), p, context = QPointer(context)]( + auto continuation = [func = std::forward(func), promise = QPromise(fi), + context = QPointer(context)]( const QFutureInterfaceBase &parentData) mutable { Q_ASSERT(context); const auto parent = QFutureInterface(parentData).future(); - QMetaObject::invokeMethod(context, [func = std::forward(func), p, parent]() mutable { - SyncContinuation continuationJob( - std::forward(func), parent, p); - continuationJob.execute(); - }); + QMetaObject::invokeMethod( + context, + [func = std::forward(func), promise = std::move(promise), parent]() mutable { + SyncContinuation continuationJob( + std::forward(func), parent, std::move(promise)); + continuationJob.execute(); + }); }; - - if constexpr (!std::is_copy_constructible_v) - f->d.setContinuation(ContinuationWrapper(std::move(continuation)), p.d); - else - f->d.setContinuation(std::move(continuation), p.d); + f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); } template @@ -686,32 +679,27 @@ template template void Continuation::fulfillPromise(Args &&... args) { - if constexpr (std::is_copy_constructible_v) - promise.reportResult(std::invoke(function, std::forward(args)...)); - else - promise.reportAndMoveResult(std::invoke(function, std::forward(args)...)); + promise.addResult(std::invoke(function, std::forward(args)...)); } template -void fulfillPromise(QFutureInterface &promise, QFuture &future) +void fulfillPromise(QPromise &promise, QFuture &future) { if constexpr (!std::is_void_v) { if constexpr (std::is_copy_constructible_v) - promise.reportResult(future.result()); + promise.addResult(future.result()); else - promise.reportAndMoveResult(future.takeResult()); + promise.addResult(future.takeResult()); } } template -void fulfillPromise(QFutureInterface &promise, Function &&handler) +void fulfillPromise(QPromise &promise, Function &&handler) { if constexpr (std::is_void_v) handler(); - else if constexpr (std::is_copy_constructible_v) - promise.reportResult(handler()); else - promise.reportAndMoveResult(handler()); + promise.addResult(handler()); } #ifndef QT_NO_EXCEPTIONS @@ -719,49 +707,44 @@ void fulfillPromise(QFutureInterface &promise, Function &&handler) template template void FailureHandler::create(F &&function, QFuture *future, - const QFutureInterface &promise) + const QFutureInterface &fi) { Q_ASSERT(future); - auto failureContinuation = [function = std::forward(function), - promise](const QFutureInterfaceBase &parentData) mutable { + auto failureContinuation = [function = std::forward(function), promise = QPromise(fi)]( + const QFutureInterfaceBase &parentData) mutable { const auto parent = QFutureInterface(parentData).future(); FailureHandler failureHandler(std::forward(function), - parent, promise); + parent, std::move(promise)); failureHandler.run(); }; - if constexpr (!std::is_copy_constructible_v) - future->d.setContinuation(ContinuationWrapper(std::move(failureContinuation))); - else - future->d.setContinuation(std::move(failureContinuation)); + future->d.setContinuation(ContinuationWrapper(std::move(failureContinuation))); } template template void FailureHandler::create(F &&function, QFuture *future, - QFutureInterface &promise, + QFutureInterface &fi, QObject *context) { Q_ASSERT(future); auto failureContinuation = - [function = std::forward(function), promise, + [function = std::forward(function), promise = QPromise(fi), context = QPointer(context)](const QFutureInterfaceBase &parentData) mutable { Q_ASSERT(context); const auto parent = QFutureInterface(parentData).future(); - QMetaObject::invokeMethod( - context, [function = std::forward(function), promise, parent]() mutable { - FailureHandler failureHandler( - std::forward(function), parent, promise); - failureHandler.run(); - }); + QMetaObject::invokeMethod(context, + [function = std::forward(function), + promise = std::move(promise), parent]() mutable { + FailureHandler failureHandler( + std::forward(function), parent, std::move(promise)); + failureHandler.run(); + }); }; - if constexpr (!std::is_copy_constructible_v) - future->d.setContinuation(ContinuationWrapper(std::move(failureContinuation))); - else - future->d.setContinuation(std::move(failureContinuation)); + future->d.setContinuation(ContinuationWrapper(std::move(failureContinuation))); } template @@ -769,7 +752,7 @@ void FailureHandler::run() { Q_ASSERT(parentFuture.isFinished()); - promise.reportStarted(); + promise.start(); if (parentFuture.d.hasException()) { using ArgType = typename QtPrivate::ArgResolver::First; @@ -781,7 +764,7 @@ void FailureHandler::run() } else { QtPrivate::fulfillPromise(promise, parentFuture); } - promise.reportFinished(); + promise.finish(); } template @@ -794,21 +777,17 @@ void FailureHandler::handleException() } catch (const ArgType &e) { try { // Handle exceptions matching with the handler's argument type - if constexpr (std::is_void_v) { + if constexpr (std::is_void_v) handler(e); - } else { - if constexpr (std::is_copy_constructible_v) - promise.reportResult(handler(e)); - else - promise.reportAndMoveResult(handler(e)); - } + else + promise.addResult(handler(e)); } catch (...) { - promise.reportException(std::current_exception()); + promise.setException(std::current_exception()); } } catch (...) { // Exception doesn't match with handler's argument type, propagate // the exception to be handled later. - promise.reportException(std::current_exception()); + promise.setException(std::current_exception()); } } @@ -822,7 +801,7 @@ void FailureHandler::handleAllExceptions() try { QtPrivate::fulfillPromise(promise, std::forward(handler)); } catch (...) { - promise.reportException(std::current_exception()); + promise.setException(std::current_exception()); } } } @@ -834,68 +813,55 @@ class CanceledHandler { public: template - static QFuture create(F &&handler, QFuture *future, - QFutureInterface &promise) + static void create(F &&handler, QFuture *future, QFutureInterface &fi) { Q_ASSERT(future); - auto canceledContinuation = [promise, handler = std::forward(handler)]( + auto canceledContinuation = [promise = QPromise(fi), handler = std::forward(handler)]( const QFutureInterfaceBase &parentData) mutable { auto parentFuture = QFutureInterface(parentData).future(); - run(std::forward(handler), parentFuture, promise); + run(std::forward(handler), parentFuture, std::move(promise)); }; - - if constexpr (!std::is_copy_constructible_v) - future->d.setContinuation(ContinuationWrapper(std::move(canceledContinuation))); - else - future->d.setContinuation(std::move(canceledContinuation)); - - return promise.future(); + future->d.setContinuation(ContinuationWrapper(std::move(canceledContinuation))); } template - static QFuture create(F &&handler, QFuture *future, - QFutureInterface &promise, QObject *context) + static void create(F &&handler, QFuture *future, QFutureInterface &fi, + QObject *context) { Q_ASSERT(future); - - auto canceledContinuation = [promise, handler = std::forward(handler), + auto canceledContinuation = [promise = QPromise(fi), handler = std::forward(handler), context = QPointer(context)]( const QFutureInterfaceBase &parentData) mutable { Q_ASSERT(context); auto parentFuture = QFutureInterface(parentData).future(); - QMetaObject::invokeMethod( - context, [promise, parentFuture, handler = std::forward(handler)]() mutable { - run(std::forward(handler), parentFuture, promise); - }); + QMetaObject::invokeMethod(context, + [promise = std::move(promise), parentFuture, + handler = std::forward(handler)]() mutable { + run(std::forward(handler), parentFuture, std::move(promise)); + }); }; - if constexpr (!std::is_copy_constructible_v) - future->d.setContinuation(ContinuationWrapper(std::move(canceledContinuation))); - else - future->d.setContinuation(std::move(canceledContinuation)); - - return promise.future(); + future->d.setContinuation(ContinuationWrapper(std::move(canceledContinuation))); } template - static void run(F &&handler, QFuture &parentFuture, - QFutureInterface &promise) + static void run(F &&handler, QFuture &parentFuture, QPromise &&promise) { - promise.reportStarted(); + promise.start(); if (parentFuture.isCanceled()) { #ifndef QT_NO_EXCEPTIONS if (parentFuture.d.hasException()) { // Propagate the exception to the result future - promise.reportException(parentFuture.d.exceptionStore().exception()); + promise.setException(parentFuture.d.exceptionStore().exception()); } else { try { #endif QtPrivate::fulfillPromise(promise, std::forward(handler)); #ifndef QT_NO_EXCEPTIONS } catch (...) { - promise.reportException(std::current_exception()); + promise.setException(std::current_exception()); } } #endif @@ -903,7 +869,7 @@ public: QtPrivate::fulfillPromise(promise, parentFuture); } - promise.reportFinished(); + promise.finish(); } }; diff --git a/tests/benchmarks/corelib/thread/qfuture/tst_bench_qfuture.cpp b/tests/benchmarks/corelib/thread/qfuture/tst_bench_qfuture.cpp index 17c2f20712..686bb76bdb 100644 --- a/tests/benchmarks/corelib/thread/qfuture/tst_bench_qfuture.cpp +++ b/tests/benchmarks/corelib/thread/qfuture/tst_bench_qfuture.cpp @@ -30,6 +30,7 @@ #include #include +#include #include class tst_QFuture : public QObject