Optimize QPromise destructor

Unify cancel and finish in QPromise destructor in a single call. This
saves us one extra mutex lock and atomic state change.

Task-number: QTBUG-84977
Change-Id: Iac06302c39a2863008b27325fcf6792d4f58c8ae
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Sona Kurazyan 2021-10-06 10:04:54 +02:00
parent 22248a4e96
commit ce59ccb334
5 changed files with 106 additions and 9 deletions

View File

@ -110,14 +110,36 @@ static inline int switch_from_to(QAtomicInt &a, int from, int to)
void QFutureInterfaceBase::cancel() void QFutureInterfaceBase::cancel()
{ {
QMutexLocker locker(&d->m_mutex); cancel(CancelMode::CancelOnly);
if (d->state.loadRelaxed() & Canceled) }
return;
void QFutureInterfaceBase::cancel(QFutureInterfaceBase::CancelMode mode)
{
QMutexLocker locker(&d->m_mutex);
const auto oldState = d->state.loadRelaxed();
switch (mode) {
case CancelMode::CancelAndFinish:
if ((oldState & Finished) && (oldState & Canceled))
return;
switch_from_to(d->state, suspendingOrSuspended | Running, Canceled | Finished);
break;
case CancelMode::CancelOnly:
if (oldState & Canceled)
return;
switch_from_to(d->state, suspendingOrSuspended, Canceled);
break;
}
switch_from_to(d->state, suspendingOrSuspended, Canceled);
d->waitCondition.wakeAll(); d->waitCondition.wakeAll();
d->pausedWaitCondition.wakeAll(); d->pausedWaitCondition.wakeAll();
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
if (!(oldState & Canceled))
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
if (mode == CancelMode::CancelAndFinish && !(oldState & Finished))
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Finished));
d->isValid = false; d->isValid = false;
} }

View File

@ -154,6 +154,8 @@ public:
int loadState() const; int loadState() const;
void cancel(); void cancel();
void cancelAndFinish() { cancel(CancelMode::CancelAndFinish); }
void setSuspended(bool suspend); void setSuspended(bool suspend);
void toggleSuspended(); void toggleSuspended();
void reportSuspended() const; void reportSuspended() const;
@ -216,6 +218,9 @@ protected:
bool launchAsync() const; bool launchAsync() const;
bool isRunningOrPending() const; bool isRunningOrPending() const;
enum class CancelMode { CancelOnly, CancelAndFinish };
void cancel(CancelMode mode);
}; };
inline void swap(QFutureInterfaceBase &lhs, QFutureInterfaceBase &rhs) noexcept inline void swap(QFutureInterfaceBase &lhs, QFutureInterfaceBase &rhs) noexcept

View File

@ -70,10 +70,8 @@ public:
return; return;
// Otherwise, if computation is not finished at this point, cancel // Otherwise, if computation is not finished at this point, cancel
// potential waits // potential waits
if (!(state & QFutureInterfaceBase::State::Finished)) { if (!(state & QFutureInterfaceBase::State::Finished))
d.cancel(); d.cancelAndFinish(); // cancel and finalize the state
finish(); // required to finalize the state
}
} }
// Core QPromise APIs // Core QPromise APIs

View File

@ -107,6 +107,7 @@ private slots:
void futureInterface(); void futureInterface();
void refcounting(); void refcounting();
void cancel(); void cancel();
void cancelAndFinish();
void statePropagation(); void statePropagation();
void multipleResults(); void multipleResults();
void indexedResults(); void indexedResults();
@ -865,6 +866,39 @@ void tst_QFuture::cancel()
} }
} }
void tst_QFuture::cancelAndFinish()
{
{
QFutureInterface<void> fi;
fi.reportStarted();
fi.cancelAndFinish();
QVERIFY(fi.isStarted());
QVERIFY(!fi.isRunning());
QVERIFY(!fi.isSuspended());
QVERIFY(!fi.isSuspending());
QVERIFY(fi.isCanceled());
QVERIFY(fi.isFinished());
}
// The same with suspended state
{
QFutureInterface<void> fi;
fi.reportStarted();
fi.setSuspended(true);
fi.cancelAndFinish();
QVERIFY(fi.isStarted());
QVERIFY(!fi.isRunning());
QVERIFY(!fi.isSuspended());
QVERIFY(!fi.isSuspending());
QVERIFY(fi.isCanceled());
QVERIFY(fi.isFinished());
}
}
void tst_QFuture::statePropagation() void tst_QFuture::statePropagation()
{ {
QFuture<void> f1; QFuture<void> f1;

View File

@ -46,6 +46,8 @@ private slots:
void startFinish(); void startFinish();
void progressValueChanged(); void progressValueChanged();
void canceled(); void canceled();
void cancelAndFinish_data();
void cancelAndFinish();
void resultAt(); void resultAt();
void resultReadyAt(); void resultReadyAt();
void futureSignals(); void futureSignals();
@ -229,6 +231,42 @@ void tst_QFutureWatcher::canceled()
future.waitForFinished(); future.waitForFinished();
} }
void tst_QFutureWatcher::cancelAndFinish_data()
{
QTest::addColumn<bool>("isCanceled");
QTest::addColumn<bool>("isFinished");
QTest::addRow("running") << false << false;
QTest::addRow("canceled") << true << false;
QTest::addRow("finished") << false << true;
QTest::addRow("canceledAndFinished") << true << true;
}
void tst_QFutureWatcher::cancelAndFinish()
{
QFETCH(bool, isCanceled);
QFETCH(bool, isFinished);
QFutureInterface<void> fi;
QFutureWatcher<void> futureWatcher;
QSignalSpy finishedSpy(&futureWatcher, &QFutureWatcher<void>::finished);
QSignalSpy canceledSpy(&futureWatcher, &QFutureWatcher<void>::canceled);
futureWatcher.setFuture(fi.future());
fi.reportStarted();
if (isCanceled)
fi.cancel();
if (isFinished)
fi.reportFinished();
fi.cancelAndFinish();
// The signals should be emitted only once
QTRY_COMPARE(canceledSpy.count(), 1);
QTRY_COMPARE(finishedSpy.count(), 1);
}
class IntTask : public RunFunctionTaskBase<int> class IntTask : public RunFunctionTaskBase<int>
{ {
public: public: