summaryrefslogtreecommitdiffstats
path: root/src/corelib/thread
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2020-03-30 10:46:00 +0200
committerSona Kurazyan <sona.kurazyan@qt.io>2020-04-01 21:51:13 +0200
commit0f78e85421de113d73edf0d58b34e12ad188417c (patch)
tree08d12c70cb555c6514d2ad202b79389f9b50f8c4 /src/corelib/thread
parent495f958b9ac5c45196e743fcba300fcfd25b90a3 (diff)
Add support of failure handler callbacks to QFuture
Added QFuture::onFailed() method, which allows attaching handlers for exceptions that may occur in QFuture continuation chains. Task-number: QTBUG-81588 Change-Id: Iadeee99e3a7573207f6ca9f650ff9f7b6faa2cf7 Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/corelib/thread')
-rw-r--r--src/corelib/thread/qfuture.h24
-rw-r--r--src/corelib/thread/qfuture.qdoc83
-rw-r--r--src/corelib/thread/qfuture_impl.h175
-rw-r--r--src/corelib/thread/qfutureinterface.h10
4 files changed, 291 insertions, 1 deletions
diff --git a/src/corelib/thread/qfuture.h b/src/corelib/thread/qfuture.h
index 0507cfb59e..bfca16b641 100644
--- a/src/corelib/thread/qfuture.h
+++ b/src/corelib/thread/qfuture.h
@@ -162,6 +162,12 @@ public:
template<class Function>
QFuture<ResultType<Function>> then(QThreadPool *pool, Function &&function);
+#ifndef QT_NO_EXCEPTIONS
+ template<class Function,
+ typename = std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs>>
+ QFuture<T> onFailed(Function &&handler);
+#endif
+
class const_iterator
{
public:
@@ -275,6 +281,11 @@ private:
template<class Function, class ResultType, class ParentResultType>
friend class QtPrivate::Continuation;
+#ifndef QT_NO_EXCEPTIONS
+ template<class Function, class ResultType>
+ friend class QtPrivate::FailureHandler;
+#endif
+
using QFuturePrivate =
std::conditional_t<std::is_same_v<T, void>, QFutureInterfaceBase, QFutureInterface<T>>;
@@ -335,6 +346,19 @@ QFuture<typename QFuture<T>::template ResultType<Function>> QFuture<T>::then(QTh
return promise.future();
}
+#ifndef QT_NO_EXCEPTIONS
+
+template<class T>
+template<class Function, typename>
+QFuture<T> QFuture<T>::onFailed(Function &&handler)
+{
+ QFutureInterface<T> promise(QFutureInterfaceBase::State::Pending);
+ QtPrivate::FailureHandler<Function, T>::create(std::forward<Function>(handler), this, promise);
+ return promise.future();
+}
+
+#endif
+
inline QFuture<void> QFutureInterface<void>::future()
{
return QFuture<void>(this);
diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc
index 1519f20cf0..8b354fe114 100644
--- a/src/corelib/thread/qfuture.qdoc
+++ b/src/corelib/thread/qfuture.qdoc
@@ -846,6 +846,8 @@
\note If the parent future gets canceled, its continuations will
also be canceled.
+
+ \sa onFailed()
*/
/*! \fn template<class T> template<class Function> QFuture<typename QFuture<T>::ResultType<Function>> QFuture<T>::then(QtFuture::Launch policy, Function &&function)
@@ -877,6 +879,8 @@
future.then(QtFuture::Launch::Async, [](int res){ ... })
.then(QtFuture::Launch::Inherit, [](int res2){ ... });
\endcode
+
+ \sa onFailed()
*/
/*! \fn template<class T> template<class Function> QFuture<typename QFuture<T>::ResultType<Function>> QFuture<T>::then(QThreadPool *pool, Function &&function)
@@ -888,4 +892,83 @@
computations if desired. When the asynchronous computation represented by this
future finishes, \a function will be invoked in a separate thread taken from the
QThreadPool \a pool.
+
+ \sa onFailed()
+*/
+
+/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(Function &&handler)
+
+ \since 6.0
+
+ Attaches a failure handler to this future, to handle any exceptions that may
+ have been generated. Returns a QFuture of the parent type. The handler will
+ be invoked only in case of an exception, in the same thread as the parent
+ future has been running. \a handler is a callable which takes either no argument
+ or one argument, to filter by specific error types similar to
+ \l {https://en.cppreference.com/w/cpp/language/try_catch} {catch} statement.
+
+ For example:
+
+ \code
+ QFuture<int> future = ...;
+ auto resultFuture = future.then([](int res) {
+ ...
+ throw Error();
+ ...
+ }).onFailed([](const Error &e) {
+ // Handle exceptions of type Error
+ ...
+ return -1;
+ }).onFailed([] {
+ // Handle all other types of errors
+ ...
+ return -1;
+ });
+
+ auto result = resultFuture.result(); // result is -1
+
+ \endcode
+
+ If there are multiple handlers attached, the first handler that matches with the
+ thrown exception type will be invoked. For example:
+
+ \code
+ QFuture<int> future = ...;
+ future.then([](int res) {
+ ...
+ throw std::runtime_error("message");
+ ...
+ }).onFailed([](const std::exception &e) {
+ // This handler will be invoked
+ }).onFailed([](const std::runtime_error &e) {
+ // This handler won't be invoked, because of the handler above.
+ });
+ \endcode
+
+ If none of the handlers matches with the thrown exception type, the exception
+ will be propagated to the resulted future:
+
+ \code
+ QFuture<int> future = ...;
+ auto resultFuture = future.then([](int res) {
+ ...
+ throw Error("message");
+ ...
+ }).onFailed([](const std::exception &e) {
+ // Won't be invoked
+ }).onFailed([](const QException &e) {
+ // Won't be invoked
+ });
+
+ try {
+ auto result = resultFuture.result();
+ } catch(...) {
+ // Handle the exception
+ }
+ \endcode
+
+ \note You can always attach a handler taking no argument, to handle all exception
+ types and avoid writing the try-catch block.
+
+ \sa then()
*/
diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h
index 903b3787e3..4a57ba7846 100644
--- a/src/corelib/thread/qfuture_impl.h
+++ b/src/corelib/thread/qfuture_impl.h
@@ -109,6 +109,64 @@ struct ResultTypeHelper<
using ResultType = std::invoke_result_t<std::decay_t<F>>;
};
+// Helpers to resolve argument types of callables.
+template<typename...>
+struct ArgsType;
+
+template<typename Arg, typename... Args>
+struct ArgsType<Arg, Args...>
+{
+ using First = Arg;
+ static const bool HasExtraArgs = (sizeof...(Args) > 0);
+};
+
+template<>
+struct ArgsType<>
+{
+ using First = void;
+ static const bool HasExtraArgs = false;
+};
+
+template<typename F>
+struct ArgResolver : ArgResolver<decltype(&std::decay_t<F>::operator())>
+{
+};
+
+template<typename R, typename... Args>
+struct ArgResolver<R(Args...)> : public ArgsType<Args...>
+{
+};
+
+template<typename R, typename... Args>
+struct ArgResolver<R (*)(Args...)> : public ArgsType<Args...>
+{
+};
+
+template<typename R, typename... Args>
+struct ArgResolver<R (&)(Args...)> : public ArgsType<Args...>
+{
+};
+
+template<typename Class, typename R, typename... Args>
+struct ArgResolver<R (Class::*)(Args...)> : public ArgsType<Args...>
+{
+};
+
+template<typename Class, typename R, typename... Args>
+struct ArgResolver<R (Class::*)(Args...) noexcept> : public ArgsType<Args...>
+{
+};
+
+template<typename Class, typename R, typename... Args>
+struct ArgResolver<R (Class::*)(Args...) const> : public ArgsType<Args...>
+{
+};
+
+template<typename Class, typename R, typename... Args>
+struct ArgResolver<R (Class::*)(Args...) const noexcept> : public ArgsType<Args...>
+{
+};
+
template<typename Function, typename ResultType, typename ParentResultType>
class Continuation
{
@@ -186,6 +244,37 @@ private:
QThreadPool *threadPool;
};
+#ifndef QT_NO_EXCEPTIONS
+
+template<class Function, class ResultType>
+class FailureHandler
+{
+public:
+ static void create(Function &&function, QFuture<ResultType> *future,
+ const QFutureInterface<ResultType> &promise);
+
+ FailureHandler(Function &&func, const QFuture<ResultType> &f,
+ const QFutureInterface<ResultType> &p)
+ : promise(p), parentFuture(f), handler(std::forward<Function>(func))
+ {
+ }
+
+public:
+ void run();
+
+private:
+ template<class ArgType>
+ void handleException();
+ void handleAllExceptions();
+
+private:
+ QFutureInterface<ResultType> promise;
+ const QFuture<ResultType> parentFuture;
+ Function handler;
+};
+
+#endif
+
template<typename Function, typename ResultType, typename ParentResultType>
void Continuation<Function, ResultType, ParentResultType>::runFunction()
{
@@ -297,7 +386,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(Function &&fun
p.setLaunchAsync(launchAsync);
- auto continuation = [continuationJob, policy, launchAsync]() mutable {
+ auto continuation = [continuationJob, launchAsync]() mutable {
bool isLaunched = continuationJob->execute();
// If continuation is successfully launched, AsyncContinuation will be deleted
// by the QThreadPool which has started it. Synchronous continuation will be
@@ -337,6 +426,90 @@ void Continuation<Function, ResultType, ParentResultType>::create(Function &&fun
f->d.setContinuation(continuation);
}
+#ifndef QT_NO_EXCEPTIONS
+
+template<class Function, class ResultType>
+void FailureHandler<Function, ResultType>::create(Function &&function, QFuture<ResultType> *future,
+ const QFutureInterface<ResultType> &promise)
+{
+ Q_ASSERT(future);
+
+ FailureHandler<Function, ResultType> *failureHandler = new FailureHandler<Function, ResultType>(
+ std::forward<Function>(function), *future, promise);
+
+ auto failureContinuation = [failureHandler]() mutable {
+ failureHandler->run();
+ delete failureHandler;
+ };
+
+ future->d.setContinuation(std::move(failureContinuation));
+}
+
+template<class Function, class ResultType>
+void FailureHandler<Function, ResultType>::run()
+{
+ Q_ASSERT(parentFuture.isFinished());
+
+ promise.reportStarted();
+
+ if (parentFuture.d.exceptionStore().hasException()) {
+ using ArgType = typename QtPrivate::ArgResolver<Function>::First;
+ if constexpr (std::is_void_v<ArgType>) {
+ handleAllExceptions();
+ } else {
+ handleException<ArgType>();
+ }
+ } else {
+ if constexpr (!std::is_void_v<ResultType>)
+ promise.reportResult(parentFuture.result());
+ }
+ promise.reportFinished();
+}
+
+template<class Function, class ResultType>
+template<class ArgType>
+void FailureHandler<Function, ResultType>::handleException()
+{
+ try {
+ parentFuture.d.exceptionStore().throwPossibleException();
+ } catch (const ArgType &e) {
+ try {
+ // Handle exceptions matching with the handler's argument type
+ if constexpr (std::is_void_v<ResultType>) {
+ handler(e);
+ } else {
+ promise.reportResult(handler(e));
+ }
+ } catch (...) {
+ promise.reportException(std::current_exception());
+ }
+ } catch (...) {
+ // Exception doesn't match with handler's argument type, propagate
+ // the exception to be handled later.
+ promise.reportException(std::current_exception());
+ }
+}
+
+template<class Function, class ResultType>
+void FailureHandler<Function, ResultType>::handleAllExceptions()
+{
+ try {
+ parentFuture.d.exceptionStore().throwPossibleException();
+ } catch (...) {
+ try {
+ if constexpr (std::is_void_v<ResultType>) {
+ handler();
+ } else {
+ promise.reportResult(handler());
+ }
+ } catch (...) {
+ promise.reportException(std::current_exception());
+ }
+ }
+}
+
+#endif // QT_NO_EXCEPTIONS
+
} // namespace QtPrivate
QT_END_NAMESPACE
diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h
index c8298d0742..04828609ba 100644
--- a/src/corelib/thread/qfutureinterface.h
+++ b/src/corelib/thread/qfutureinterface.h
@@ -63,6 +63,11 @@ class QFutureWatcherBasePrivate;
namespace QtPrivate {
template<typename Function, typename ResultType, typename ParentResultType>
class Continuation;
+
+#ifndef QT_NO_EXCEPTIONS
+template<class Function, class ResultType>
+class FailureHandler;
+#endif
}
class Q_CORE_EXPORT QFutureInterfaceBase
@@ -158,6 +163,11 @@ private:
template<typename Function, typename ResultType, typename ParentResultType>
friend class QtPrivate::Continuation;
+#ifndef QT_NO_EXCEPTIONS
+ template<class Function, class ResultType>
+ friend class QtPrivate::FailureHandler;
+#endif
+
protected:
void setContinuation(std::function<void()> func);
void runContinuation() const;