diff options
author | Sona Kurazyan <sona.kurazyan@qt.io> | 2021-11-10 09:36:26 +0100 |
---|---|---|
committer | Sona Kurazyan <sona.kurazyan@qt.io> | 2021-11-13 18:13:08 +0100 |
commit | eab40726be98616b7edac3a600e97b39fc6227cd (patch) | |
tree | 4c20257196c01d3152c0ff71d737bd6041a2dd38 | |
parent | 746644a5a9631896452ec44ff7e1ffc80064b848 (diff) |
QFuture: support cancellation of continuation chain through parent
This change allows canceling the chain of continuations attached to a
future through canceling the future itself at any point of execution of
the chain.
[ChangeLog][QtCore][Important Behavior Changes] The chain of
continuations attached to a future now can be cancelled through
cancelling the future itself at any point of the execution of the chain,
as it was documented. Previously canceling the future would cancel the
chain only if it was done before the chain starts executing, otherwise
the cancellation would be ignored. Now the part of the chain that wasn't
started at the moment of cancellation will be canceled.
Task-number: QTBUG-97582
Change-Id: I4c3b3c68e34d3a044243ac9a7a9ed3c38b7cb02e
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r-- | src/corelib/thread/qfuture_impl.h | 8 | ||||
-rw-r--r-- | src/corelib/thread/qfutureinterface.cpp | 28 | ||||
-rw-r--r-- | src/corelib/thread/qfutureinterface.h | 4 | ||||
-rw-r--r-- | src/corelib/thread/qfutureinterface_p.h | 1 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 126 |
5 files changed, 163 insertions, 4 deletions
diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 511caafdf7..d7132caa7b 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -425,7 +425,7 @@ bool Continuation<Function, ResultType, ParentResultType>::execute() { Q_ASSERT(parentFuture.isFinished()); - if (parentFuture.isCanceled()) { + if (parentFuture.d.isChainCanceled()) { #ifndef QT_NO_EXCEPTIONS if (parentFuture.d.hasException()) { // If the continuation doesn't take a QFuture argument, propagate the exception @@ -498,7 +498,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func, } }; - f->d.setContinuation(std::move(continuation)); + f->d.setContinuation(std::move(continuation), p.d); } template<typename Function, typename ResultType, typename ParentResultType> @@ -527,7 +527,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func, } }; - f->d.setContinuation(std::move(continuation)); + f->d.setContinuation(std::move(continuation), p.d); } template<typename Function, typename ResultType, typename ParentResultType> @@ -550,7 +550,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func, }); }; - f->d.setContinuation(std::move(continuation)); + f->d.setContinuation(std::move(continuation), p.d); } template<typename Function, typename ResultType, typename ParentResultType> diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp index e640114bb0..7512d6174a 100644 --- a/src/corelib/thread/qfutureinterface.cpp +++ b/src/corelib/thread/qfutureinterface.cpp @@ -835,7 +835,17 @@ void QFutureInterfaceBasePrivate::setState(QFutureInterfaceBase::State newState) void QFutureInterfaceBase::setContinuation(std::function<void(const QFutureInterfaceBase &)> func) { + setContinuation(func, nullptr); +} + +void QFutureInterfaceBase::setContinuation(std::function<void(const QFutureInterfaceBase &)> func, + QFutureInterfaceBasePrivate *continuationFutureData) +{ QMutexLocker lock(&d->continuationMutex); + + if (continuationFutureData) + continuationFutureData->parentData = d; + // If the state is ready, run continuation immediately, // otherwise save it for later. if (isFinished()) { @@ -855,6 +865,24 @@ void QFutureInterfaceBase::runContinuation() const } } +bool QFutureInterfaceBase::isChainCanceled() const +{ + if (isCanceled()) + return true; + + auto parent = d->parentData; + while (parent) { + // If the future is in Canceled state because it had an exception, we want to + // continue checking the chain of parents for cancellation, otherwise if the exception + // is handeled inside the chain, it won't be interrupted even though cancellation has + // been requested. + if ((parent->state.loadRelaxed() & Canceled) && !parent->hasException) + return true; + parent = parent->parentData; + } + return false; +} + void QFutureInterfaceBase::setLaunchAsync(bool value) { d->launchAsync = value; diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h index e813031d59..3130c91420 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -182,6 +182,8 @@ public: template<typename T> static QFutureInterfaceBase get(const QFuture<T> &future); // implemented in qfuture.h + bool isChainCanceled() const; + protected: // ### Qt 7: remove const from refT/derefT bool refT() const noexcept; @@ -212,6 +214,8 @@ private: protected: void setContinuation(std::function<void(const QFutureInterfaceBase &)> func); + void setContinuation(std::function<void(const QFutureInterfaceBase &)> func, + QFutureInterfaceBasePrivate *continuationFutureData); void runContinuation() const; void setLaunchAsync(bool value); diff --git a/src/corelib/thread/qfutureinterface_p.h b/src/corelib/thread/qfutureinterface_p.h index c92f8e2230..fd7f34826d 100644 --- a/src/corelib/thread/qfutureinterface_p.h +++ b/src/corelib/thread/qfutureinterface_p.h @@ -190,6 +190,7 @@ public: QThreadPool *m_pool = nullptr; // Wrapper for continuation std::function<void(const QFutureInterfaceBase &)> continuation; + QFutureInterfaceBasePrivate *parentData = nullptr; RefCount refCount = 1; QAtomicInt state; // reads and writes can happen unprotected, both must be atomic diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index a6be2e9727..9c8bd232d5 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -143,6 +143,7 @@ private slots: void onFailedForMoveOnlyTypes(); #endif void onCanceled(); + void cancelContinuations(); void continuationsWithContext(); #if 0 // TODO: enable when QFuture::takeResults() is enabled @@ -2882,6 +2883,131 @@ void tst_QFuture::onCanceled() #endif // QT_NO_EXCEPTIONS } +void tst_QFuture::cancelContinuations() +{ + // The chain is cancelled in the middle of execution of continuations + { + QPromise<int> promise; + + int checkpoint = 0; + auto future = promise.future().then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + promise.future().cancel(); + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + promise.start(); + promise.addResult(42); + promise.finish(); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 2); + } + + // The chain is cancelled before the execution of continuations + { + auto f = QtFuture::makeReadyFuture(42); + f.cancel(); + + int checkpoint = 0; + auto future = f.then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 0); + } + + // The chain is canceled partially, through an intermediate future + { + QPromise<int> promise; + + int checkpoint = 0; + auto intermediate = promise.future().then([&](int value) { + ++checkpoint; + return value + 1; + }); + + auto future = intermediate.then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + promise.start(); + promise.addResult(42); + + // This should cancel only the chain starting from intermediate + intermediate.cancel(); + + promise.finish(); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 1); + } + +#ifndef QT_NO_EXCEPTIONS + // The chain is cancelled in the middle of execution of continuations, + // while there's an exception in the chain, which is handeled inside + // the continuations. + { + QPromise<int> promise; + + int checkpoint = 0; + auto future = promise.future().then([&](int value) { + ++checkpoint; + throw QException(); + return value + 1; + }).then([&](QFuture<int> future) { + try { + auto res = future.result(); + Q_UNUSED(res); + } catch (const QException &) { + ++checkpoint; + } + return 2; + }).then([&](int value) { + ++checkpoint; + promise.future().cancel(); + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + promise.start(); + promise.addResult(42); + promise.finish(); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 3); + } +#endif // QT_NO_EXCEPTIONS +} + void tst_QFuture::continuationsWithContext() { QThread thread; |