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>
This commit is contained in:
Sona Kurazyan 2020-03-30 10:46:00 +02:00
parent 495f958b9a
commit 0f78e85421
5 changed files with 624 additions and 1 deletions

View File

@ -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);

View File

@ -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()
*/

View File

@ -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

View File

@ -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;

View File

@ -104,6 +104,8 @@ private slots:
#ifndef QT_NO_EXCEPTIONS
void thenOnExceptionFuture();
void thenThrows();
void onFailed();
void onFailedTestCallables();
#endif
void takeResults();
void takeResult();
@ -2149,6 +2151,337 @@ void tst_QFuture::thenThrows()
}
}
void tst_QFuture::onFailed()
{
// Ready exception void future
{
int checkpoint = 0;
auto future = createExceptionFuture().then([&] { checkpoint = 1; }).onFailed([&] {
checkpoint = 2;
});
try {
future.waitForFinished();
} catch (...) {
checkpoint = 3;
}
QCOMPARE(checkpoint, 2);
}
// std::exception handler
{
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw std::exception();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&](const QException &) {
checkpoint = 1;
return -1;
})
.onFailed([&](const std::exception &) {
checkpoint = 2;
return -1;
})
.onFailed([&] {
checkpoint = 3;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 4;
}
QCOMPARE(checkpoint, 2);
QCOMPARE(res, -1);
}
// then() throws an exception derived from QException
{
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw DerivedException();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&](const QException &) {
checkpoint = 1;
return -1;
})
.onFailed([&](const std::exception &) {
checkpoint = 2;
return -1;
})
.onFailed([&] {
checkpoint = 3;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 4;
}
QCOMPARE(checkpoint, 1);
QCOMPARE(res, -1);
}
// then() throws a custom exception
{
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw TestException();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&](const QException &) {
checkpoint = 1;
return -1;
})
.onFailed([&](const std::exception &) {
checkpoint = 2;
return -1;
})
.onFailed([&] {
checkpoint = 3;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 4;
}
QCOMPARE(checkpoint, 3);
QCOMPARE(res, -1);
}
// Custom exception handler
{
struct TestException
{
};
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw TestException();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&](const QException &) {
checkpoint = 1;
return -1;
})
.onFailed([&](const TestException &) {
checkpoint = 2;
return -1;
})
.onFailed([&] {
checkpoint = 3;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 4;
}
QCOMPARE(checkpoint, 2);
QCOMPARE(res, -1);
}
// Handle all exceptions
{
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw QException();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&] {
checkpoint = 1;
return -1;
})
.onFailed([&](const QException &) {
checkpoint = 2;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 3;
}
QCOMPARE(checkpoint, 1);
QCOMPARE(res, -1);
}
// Handler throws exception
{
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw QException();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&](const QException &) {
checkpoint = 1;
throw QException();
return -1;
})
.onFailed([&] {
checkpoint = 2;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 3;
}
QCOMPARE(checkpoint, 2);
QCOMPARE(res, -1);
}
// No handler for exception
{
QFutureInterface<int> promise;
int checkpoint = 0;
auto then = promise.future()
.then([&](int res) {
throw QException();
return res;
})
.then([&](int res) { return res + 1; })
.onFailed([&](const std::exception &) {
checkpoint = 1;
throw std::exception();
return -1;
})
.onFailed([&](QException &) {
checkpoint = 2;
return -1;
});
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
int res = 0;
try {
res = then.result();
} catch (...) {
checkpoint = 3;
}
QCOMPARE(checkpoint, 3);
QCOMPARE(res, 0);
}
}
template<class Callable>
bool runForCallable(Callable &&handler)
{
QFuture<int> future = createExceptionResultFuture()
.then([&](int) { return 1; })
.onFailed(std::forward<Callable>(handler));
int res = 0;
try {
res = future.result();
} catch (...) {
return false;
}
return res == -1;
}
int foo()
{
return -1;
}
void tst_QFuture::onFailedTestCallables()
{
QVERIFY(runForCallable([&] { return -1; }));
QVERIFY(runForCallable(foo));
QVERIFY(runForCallable(&foo));
std::function<int()> func = foo;
QVERIFY(runForCallable(func));
struct Functor1
{
int operator()() { return -1; }
static int foo() { return -1; }
};
QVERIFY(runForCallable(Functor1()));
QVERIFY(runForCallable(Functor1::foo));
struct Functor2
{
int operator()() const { return -1; }
static int foo() { return -1; }
};
QVERIFY(runForCallable(Functor2()));
struct Functor3
{
int operator()() const noexcept { return -1; }
static int foo() { return -1; }
};
QVERIFY(runForCallable(Functor3()));
}
#endif // QT_NO_EXCEPTIONS
void tst_QFuture::testSingleResult(const UniquePtr &p)