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.8 6.5 6.2
Fixes: QTBUG-128405
Change-Id: I4610a022ea12e1bc9ce24cb17b972b5b9e051f0a
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
Marc Mutz 2024-10-30 12:50:47 +01:00
parent 076445ec0d
commit 6efec3850d
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
execution.
\note This method must not be used after QFuture::cancel() or
\note This method has no effect after QFuture::cancel() or
finish().
\sa isCanceled()

View File

@ -9,13 +9,22 @@
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qpromise.h>
#include <QtCore/qsemaphore.h>
#include <algorithm>
#include <memory>
#include <QtCore/q20type_traits.h>
#include <chrono>
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
{
Q_OBJECT
@ -28,6 +37,8 @@ private slots:
void addResultOutOfOrder();
#ifndef QT_NO_EXCEPTIONS
void setException();
void setExceptionAfterCancelDoesNothing(); // QTBUG-128405
void setExceptionAfterFinishDoesNothing();
#endif
void cancel();
void progress();
@ -320,6 +331,69 @@ void tst_QPromise::setException()
RUN_TEST_FUNC(testExceptionCaught, QPromise<MoveOnlyType>(),
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
void tst_QPromise::cancel()