QPromise: fix documentation re:setException() after cancel() or finish()

The implementation checks the state and returns without storing the
exception when its canceled or finished.

Add tests for both situations.

Done-with: Ivan Solovev <ivan.solovev@qt.io> (analysis and phrasing)
Pick-to: 6.5 6.2
Fixes: QTBUG-128405
Change-Id: I4610a022ea12e1bc9ce24cb17b972b5b9e051f0a
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
(cherry picked from commit 6efec3850da188d3bba075185aa6e5c264c815eb)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Marc Mutz 2024-10-30 12:50:47 +01:00 committed by Qt Cherry-pick Bot
parent a11b0a3d29
commit 5a9c513231
2 changed files with 75 additions and 1 deletions

View File

@ -162,7 +162,7 @@
\note You can set at most one exception throughout the computation \note You can set at most one exception throughout the computation
execution. execution.
\note This method must not be used after QFuture::cancel() or \note This method has no effect after QFuture::cancel() or
finish(). finish().
\sa isCanceled() \sa isCanceled()

View File

@ -9,13 +9,22 @@
#include <qfuture.h> #include <qfuture.h>
#include <qfuturewatcher.h> #include <qfuturewatcher.h>
#include <qpromise.h> #include <qpromise.h>
#include <QtCore/qsemaphore.h>
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <QtCore/q20type_traits.h>
#include <chrono> #include <chrono>
using namespace std::chrono_literals; using namespace std::chrono_literals;
template <typename T>
struct promise_type {};
template <typename T>
struct promise_type<QPromise<T>> : q20::type_identity<T> {};
template <typename T>
using promise_type_t = typename promise_type<T>::type;
class tst_QPromise : public QObject class tst_QPromise : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -28,6 +37,8 @@ private slots:
void addResultOutOfOrder(); void addResultOutOfOrder();
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
void setException(); void setException();
void setExceptionAfterCancelDoesNothing(); // QTBUG-128405
void setExceptionAfterFinishDoesNothing();
#endif #endif
void cancel(); void cancel();
void progress(); void progress();
@ -320,6 +331,69 @@ void tst_QPromise::setException()
RUN_TEST_FUNC(testExceptionCaught, QPromise<MoveOnlyType>(), RUN_TEST_FUNC(testExceptionCaught, QPromise<MoveOnlyType>(),
std::make_exception_ptr(TestException())); std::make_exception_ptr(TestException()));
} }
void tst_QPromise::setExceptionAfterCancelDoesNothing()
{
struct TestException {};
auto test = [](auto promise, auto exception) {
auto f = promise.future();
FutureWatcher r(f);
QSemaphore sem;
QSemaphoreReleaser rel(sem);
ThreadWrapper t([&] {
auto p = std::move(promise);
p.start();
sem.acquire(); // wait for cancel() in main thread
p.setException(std::move(exception));
});
f.cancel();
rel.cancel()->release(); // give a go for setException() in worker thread
t.join();
QVERIFY(f.isCanceled());
QVERIFY(!r.thenCalled);
QVERIFY(!r.onFailedCalled);
QVERIFY(r.onCanceledCalled);
};
RUN_TEST_FUNC(test, QPromise<void>(), QException());
RUN_TEST_FUNC(test, QPromise<int>(), QException());
RUN_TEST_FUNC(test, QPromise<void>(), std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(test, QPromise<int>(), std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(test, QPromise<CopyOnlyType>(), std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(test, QPromise<MoveOnlyType>(), std::make_exception_ptr(TestException()));
}
void tst_QPromise::setExceptionAfterFinishDoesNothing()
{
struct TestException {};
auto test = [](auto promise, auto exception) {
auto f = promise.future();
FutureWatcher r(f);
promise.start();
using T = promise_type_t<decltype(promise)>;
if constexpr (!std::is_void_v<T>)
promise.addResult(T());
promise.finish();
promise.setException(std::move(exception));
QVERIFY(f.isFinished());
QVERIFY(r.thenCalled);
QVERIFY(!r.onFailedCalled);
QVERIFY(!r.onCanceledCalled);
};
RUN_TEST_FUNC(test, QPromise<void>(), QException());
RUN_TEST_FUNC(test, QPromise<int>(), QException());
RUN_TEST_FUNC(test, QPromise<void>(), std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(test, QPromise<int>(), std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(test, QPromise<CopyOnlyType>(), std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(test, QPromise<MoveOnlyType>(), std::make_exception_ptr(TestException()));
}
#endif #endif
void tst_QPromise::cancel() void tst_QPromise::cancel()