mirror of
https://github.com/Naios/continuable.git
synced 2025-12-06 08:46:44 +08:00
Handle the cancellation of continuables used in co_await expression correctly
* Throws a await_canceled_exception now which automatically gets converted to a default exception type again if it becomes unhandled in the handler. * Ref #32
This commit is contained in:
parent
8e63a45840
commit
5f84de0e86
@ -36,15 +36,45 @@
|
||||
#include <continuable/detail/features.hpp>
|
||||
|
||||
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
||||
#include <exception>
|
||||
# include <exception>
|
||||
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
||||
|
||||
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
||||
#include <continuable/detail/other/coroutines.hpp>
|
||||
# include <continuable/detail/other/coroutines.hpp>
|
||||
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
||||
|
||||
#if defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
|
||||
namespace cti {
|
||||
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
||||
/// Is thrown from co_await expressions if the awaited continuable is canceled
|
||||
///
|
||||
/// Default constructed exception types that are returned by a cancelled
|
||||
/// continuable are converted automatically to await_canceled_exception when
|
||||
/// being returned by a co_await expression.
|
||||
///
|
||||
/// The await_canceled_exception gets converted again to a default constructed
|
||||
/// exception type if it becomes unhandled inside a coroutine which
|
||||
/// returns a continuable_base.
|
||||
/// ```cpp
|
||||
/// continuable<> cancelled_coroutine() {
|
||||
/// co_await make_cancelling_continuable<void>();
|
||||
///
|
||||
/// co_return;
|
||||
/// }
|
||||
///
|
||||
/// // ...
|
||||
///
|
||||
/// cancelled_coroutine().fail([](exception_t e) {
|
||||
/// assert(bool(e) == false);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// \since 4.1.0
|
||||
using await_canceled_exception = detail::awaiting::await_canceled_exception;
|
||||
# endif // CONTINUABLE_HAS_EXCEPTIONS
|
||||
} // namespace cti
|
||||
|
||||
/// \cond false
|
||||
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
||||
// As far as I know there is no other way to implement this specialization...
|
||||
// NOLINTNEXTLINE(cert-dcl58-cpp)
|
||||
namespace std {
|
||||
@ -54,13 +84,12 @@ struct coroutine_traits<
|
||||
cti::continuable_base<Data, cti::detail::identity<Args...>>,
|
||||
FunctionArgs...> {
|
||||
|
||||
using promise_type =
|
||||
cti::detail::awaiting::promise_type<cti::continuable<Args...>,
|
||||
cti::promise<Args...>, Args...>;
|
||||
using promise_type = cti::detail::awaiting::promise_type<
|
||||
cti::continuable<Args...>, cti::promise<Args...>, Args...>;
|
||||
};
|
||||
} // namespace experimental
|
||||
} // namespace std
|
||||
/// \endcond
|
||||
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
||||
/// \endcond
|
||||
|
||||
#endif // CONTINUABLE_COROUTINE_HPP_INCLUDED
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
#include <continuable/detail/utility/util.hpp>
|
||||
|
||||
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
||||
#include <exception>
|
||||
# include <exception>
|
||||
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
||||
|
||||
namespace cti {
|
||||
@ -54,6 +54,17 @@ namespace awaiting {
|
||||
/// We import the coroutine handle in our namespace
|
||||
using std::experimental::coroutine_handle;
|
||||
|
||||
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
||||
class await_canceled_exception : public std::exception {
|
||||
public:
|
||||
await_canceled_exception() noexcept = default;
|
||||
|
||||
char const* what() const noexcept override {
|
||||
return "co_await canceled due to cancellation of the continuation";
|
||||
}
|
||||
};
|
||||
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
||||
|
||||
template <typename T>
|
||||
struct result_from_identity;
|
||||
template <typename... T>
|
||||
@ -76,7 +87,7 @@ class awaitable {
|
||||
|
||||
public:
|
||||
explicit constexpr awaitable(Continuable&& continuable)
|
||||
: continuable_(std::move(continuable)) {
|
||||
: continuable_(std::move(continuable)) {
|
||||
|
||||
// If the continuable is ready resolve the result from the
|
||||
// continuable immediately.
|
||||
@ -108,15 +119,21 @@ public:
|
||||
|
||||
/// Resume the coroutine represented by the handle
|
||||
typename result_t::value_t await_resume() noexcept(false) {
|
||||
if (result_) {
|
||||
if (result_.is_value()) {
|
||||
// When the result was resolved return it
|
||||
return std::move(result_).get_value();
|
||||
}
|
||||
|
||||
assert(result_.is_exception());
|
||||
|
||||
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
||||
std::rethrow_exception(result_.get_exception());
|
||||
if (exception_t e = result_.get_exception()) {
|
||||
std::rethrow_exception(std::move(e));
|
||||
} else {
|
||||
throw await_canceled_exception();
|
||||
}
|
||||
#else // CONTINUABLE_HAS_EXCEPTIONS
|
||||
// Returning error types in await isn't supported as of now
|
||||
// Returning error types from co_await isn't supported!
|
||||
CTI_DETAIL_TRAP();
|
||||
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
||||
}
|
||||
@ -141,8 +158,7 @@ struct handle_takeover {
|
||||
handle_ = handle;
|
||||
}
|
||||
|
||||
void await_resume() noexcept {
|
||||
}
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
/// The type which is passed to the compiler that describes the properties
|
||||
@ -179,7 +195,7 @@ struct promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
|
||||
|
||||
template <typename Continuable, typename Promise, typename... Args>
|
||||
struct promise_type
|
||||
: promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
|
||||
: promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
|
||||
|
||||
coroutine_handle<> handle_;
|
||||
Promise promise_;
|
||||
@ -203,9 +219,15 @@ struct promise_type
|
||||
|
||||
void unhandled_exception() noexcept {
|
||||
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
||||
promise_.set_exception(std::current_exception());
|
||||
try {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
} catch (await_canceled_exception const&) {
|
||||
promise_.set_canceled();
|
||||
} catch (...) {
|
||||
promise_.set_exception(std::current_exception());
|
||||
}
|
||||
#else // CONTINUABLE_HAS_EXCEPTIONS
|
||||
// Returning error types from coroutines isn't supported
|
||||
// Returning exception types from a coroutine isn't supported
|
||||
CTI_DETAIL_TRAP();
|
||||
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
||||
}
|
||||
|
||||
@ -26,11 +26,11 @@
|
||||
#include <continuable/detail/features.hpp>
|
||||
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
||||
|
||||
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
#include <exception>
|
||||
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
# ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
# include <exception>
|
||||
# endif // CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
|
||||
#include <tuple>
|
||||
# include <tuple>
|
||||
|
||||
/// Resolves the given promise asynchonously
|
||||
template <typename S>
|
||||
@ -76,7 +76,7 @@ TYPED_TEST(single_dimension_tests, are_awaitable) {
|
||||
EXPECT_ASYNC_RESULT(resolve_async_multiple(supply), 0, 1, 2, 3);
|
||||
}
|
||||
|
||||
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
# ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
|
||||
struct await_exception : std::exception {
|
||||
char const* what() const noexcept override {
|
||||
@ -105,7 +105,9 @@ cti::continuable<> resolve_async_exceptional(S&& supplier) {
|
||||
// GTest ASSERT_THROW isn't co_await friendly yet:
|
||||
// clang: 'return statement not allowed in coroutine; did you mean
|
||||
// 'co_return'?'
|
||||
EXPECT_THROW(co_await supplier().then([] { throw await_exception{}; }),
|
||||
EXPECT_THROW(co_await supplier().then([] {
|
||||
throw await_exception{};
|
||||
}),
|
||||
await_exception);
|
||||
|
||||
co_return;
|
||||
@ -141,6 +143,31 @@ TYPED_TEST(single_dimension_tests, are_awaitable_with_exceptions_from_coro) {
|
||||
await_exception{})
|
||||
}
|
||||
|
||||
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
template <typename S>
|
||||
cti::continuable<> resolve_coro_canceled(S&& supplier) {
|
||||
// Pseudo wait
|
||||
co_await supplier();
|
||||
|
||||
try {
|
||||
co_await cti::make_cancelling_continuable<void>();
|
||||
} catch (cti::await_canceled_exception const& e) {
|
||||
std::rethrow_exception(std::make_exception_ptr(e));
|
||||
} catch (...) {
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
TYPED_TEST(single_dimension_tests, are_awaitable_with_cancellation_from_coro) {
|
||||
auto const& supply = [&](auto&&... args) {
|
||||
// Supplies the current tested continuable
|
||||
return this->supply(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
|
||||
ASSERT_ASYNC_CANCELLATION(resolve_coro_canceled(supply))
|
||||
}
|
||||
|
||||
# endif // CONTINUABLE_WITH_NO_EXCEPTIONS
|
||||
|
||||
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user