diff options
| author | Ivan Solovev <ivan.solovev@qt.io> | 2025-01-28 18:34:26 +0100 |
|---|---|---|
| committer | Ivan Solovev <ivan.solovev@qt.io> | 2025-02-11 12:19:50 +0100 |
| commit | cdc71532ec2b5d29995f0f34249ebe2174b28c9c (patch) | |
| tree | 1abf9afc52d297c06877b0f41ae4231026575767 /src/corelib/thread/qfutureinterface.cpp | |
| parent | 3164746eb09a34818cfa874ae248d01e0c4f5d74 (diff) | |
Add QFuture::cancelChain()
This method should be used on a future returned from the long
continuation chain, if this chain needs to be canceled altogether.
To achieve it, this commit introduces a pointer to the parent data.
That pointer is set when setContinuation() is called, and cleaned
when the parent is completed and calls its runContinuation()
method. This allows the code in cancelChain() to simply move up
the chain as long as the parent pointer is valid, and cancel each
of the continuations.
The commit also moves the QFIB::cancel() implementation into
QFIBP::cancelImpl(), so that it can be reused in the new method,
and introduces an enum for cancellation options, to address the
differences in the behavior between these two methods.
Note that the approach consistently works only on the last QFuture
of the chain, because the already-executed continuations might
clear their continuation data, which makes navigating down the
chain inconsistent.
[ChangeLog][QtCore][QFuture] Added QFuture::cancelChain().
Fixes: QTBUG-130662
Change-Id: Ic0949ed30a2f54f99597e95b3951fda6bfb9a0be
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/corelib/thread/qfutureinterface.cpp')
| -rw-r--r-- | src/corelib/thread/qfutureinterface.cpp | 103 |
1 files changed, 76 insertions, 27 deletions
diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp index cc61752f28d..30d7814ab0a 100644 --- a/src/corelib/thread/qfutureinterface.cpp +++ b/src/corelib/thread/qfutureinterface.cpp @@ -95,46 +95,87 @@ static inline int switch_from_to(QAtomicInt &a, int from, int to) return value; } -void QFutureInterfaceBase::cancel() +void QFutureInterfaceBasePrivate::cancelImpl(QFutureInterfaceBase::CancelMode mode, + CancelOptions options) { - cancel(CancelMode::CancelOnly); -} - -void QFutureInterfaceBase::cancel(QFutureInterfaceBase::CancelMode mode) -{ - QMutexLocker locker(&d->m_mutex); + QMutexLocker locker(&m_mutex); - const auto oldState = d->state.loadRelaxed(); + const auto oldState = state.loadRelaxed(); switch (mode) { - case CancelMode::CancelAndFinish: - if ((oldState & Finished) && (oldState & Canceled)) + case QFutureInterfaceBase::CancelMode::CancelAndFinish: + if ((oldState & QFutureInterfaceBase::Finished) + && (oldState & QFutureInterfaceBase::Canceled)) { return; - switch_from_to(d->state, suspendingOrSuspended | Running, Canceled | Finished); + } + switch_from_to(state, suspendingOrSuspended | QFutureInterfaceBase::Running, + QFutureInterfaceBase::Canceled | QFutureInterfaceBase::Finished); break; - case CancelMode::CancelOnly: - if (oldState & Canceled) + case QFutureInterfaceBase::CancelMode::CancelOnly: + if (oldState & QFutureInterfaceBase::Canceled) return; - switch_from_to(d->state, suspendingOrSuspended, Canceled); + switch_from_to(state, suspendingOrSuspended, QFutureInterfaceBase::Canceled); break; } - // Cancel the continuations chain - QFutureInterfaceBasePrivate *next = d->continuationData; - while (next && next->continuationType == ContinuationType::Then) { - next->continuationState = QFutureInterfaceBasePrivate::Canceled; - next = next->continuationData; + if (options & CancelOption::CancelContinuations) { + // Cancel the continuations chain + QMutexLocker continuationLocker(&continuationMutex); + QFutureInterfaceBasePrivate *next = continuationData; + while (next) { + QMutexLocker nextLocker(&next->continuationMutex); + if (next->continuationType == QFutureInterfaceBase::ContinuationType::Then) { + next->continuationState = QFutureInterfaceBasePrivate::Canceled; + next = next->continuationData; + } else { + break; + } + } } - d->waitCondition.wakeAll(); - d->pausedWaitCondition.wakeAll(); + waitCondition.wakeAll(); + pausedWaitCondition.wakeAll(); - if (!(oldState & Canceled)) - d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled)); - if (mode == CancelMode::CancelAndFinish && !(oldState & Finished)) - d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Finished)); + if (!(oldState & QFutureInterfaceBase::Canceled)) + sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled)); + if (mode == QFutureInterfaceBase::CancelMode::CancelAndFinish + && !(oldState & QFutureInterfaceBase::Finished)) { + sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Finished)); + } - d->isValid = false; + isValid = false; +} + +void QFutureInterfaceBase::cancel() +{ + cancel(CancelMode::CancelOnly); +} + +void QFutureInterfaceBase::cancelChain() +{ + cancelChain(CancelMode::CancelOnly); +} + +void QFutureInterfaceBase::cancel(QFutureInterfaceBase::CancelMode mode) +{ + d->cancelImpl(mode, QFutureInterfaceBasePrivate::CancelOption::CancelContinuations); +} + +void QFutureInterfaceBase::cancelChain(QFutureInterfaceBase::CancelMode mode) +{ + // go up through the list of continuations, cancelling each of them + { + QMutexLocker locker(&d->continuationMutex); + QFutureInterfaceBasePrivate *prev = d->nonConcludedParent; + while (prev) { + // Do not cancel continuations, because we're going bottom-to-top + prev->cancelImpl(mode, QFutureInterfaceBasePrivate::CancelOption::None); + QMutexLocker prevLocker(&prev->continuationMutex); + prev = prev->nonConcludedParent; + } + } + // finally, cancel self and all next continuations + d->cancelImpl(mode, QFutureInterfaceBasePrivate::CancelOption::CancelContinuations); } void QFutureInterfaceBase::setSuspended(bool suspend) @@ -847,10 +888,14 @@ void QFutureInterfaceBase::setContinuation(std::function<void (const QFutureInte if (d->continuation) { qWarning("Adding a continuation to a future which already has a continuation. " "The existing continuation is overwritten."); + if (d->continuationData) + d->continuationData->nonConcludedParent = nullptr; } d->continuation = std::move(func); - if (futureData) + if (futureData) { futureData->continuationType = type; + futureData->nonConcludedParent = d; + } d->continuationData = futureData; Q_ASSERT_X(!futureData || futureData->continuationType != ContinuationType::Unknown, "setContinuation", "Make sure to provide a correct continuation type!"); @@ -949,6 +994,10 @@ void QFutureInterfaceBase::runContinuation() const { QMutexLocker lock(&d->continuationMutex); if (d->continuation && !d->continuationExecuted) { + // If we run the next continuation, then this future is concluded, so + // we wouldn't need to revisit it in the cancelChain() + if (d->continuationData) + d->continuationData->nonConcludedParent = nullptr; // Save the continuation in a local function, to avoid calling // a null std::function below, in case cleanContinuation() is // called from some other thread right after unlock() below. |
