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:
Denis Blank 2020-04-09 15:24:54 +02:00
parent 8e63a45840
commit 5f84de0e86
3 changed files with 102 additions and 24 deletions

View File

@ -36,15 +36,45 @@
#include <continuable/detail/features.hpp> #include <continuable/detail/features.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS) #if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception> # include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS #endif // CONTINUABLE_HAS_EXCEPTIONS
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE #ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#include <continuable/detail/other/coroutines.hpp> # include <continuable/detail/other/coroutines.hpp>
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE #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 /// \cond false
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
// As far as I know there is no other way to implement this specialization... // As far as I know there is no other way to implement this specialization...
// NOLINTNEXTLINE(cert-dcl58-cpp) // NOLINTNEXTLINE(cert-dcl58-cpp)
namespace std { namespace std {
@ -54,13 +84,12 @@ struct coroutine_traits<
cti::continuable_base<Data, cti::detail::identity<Args...>>, cti::continuable_base<Data, cti::detail::identity<Args...>>,
FunctionArgs...> { FunctionArgs...> {
using promise_type = using promise_type = cti::detail::awaiting::promise_type<
cti::detail::awaiting::promise_type<cti::continuable<Args...>, cti::continuable<Args...>, cti::promise<Args...>, Args...>;
cti::promise<Args...>, Args...>;
}; };
} // namespace experimental } // namespace experimental
} // namespace std } // namespace std
/// \endcond
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE #endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
/// \endcond
#endif // CONTINUABLE_COROUTINE_HPP_INCLUDED #endif // CONTINUABLE_COROUTINE_HPP_INCLUDED

View File

@ -45,7 +45,7 @@
#include <continuable/detail/utility/util.hpp> #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS) #if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception> # include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS #endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti { namespace cti {
@ -54,6 +54,17 @@ namespace awaiting {
/// We import the coroutine handle in our namespace /// We import the coroutine handle in our namespace
using std::experimental::coroutine_handle; 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> template <typename T>
struct result_from_identity; struct result_from_identity;
template <typename... T> template <typename... T>
@ -76,7 +87,7 @@ class awaitable {
public: public:
explicit constexpr awaitable(Continuable&& continuable) explicit constexpr awaitable(Continuable&& continuable)
: continuable_(std::move(continuable)) { : continuable_(std::move(continuable)) {
// If the continuable is ready resolve the result from the // If the continuable is ready resolve the result from the
// continuable immediately. // continuable immediately.
@ -108,15 +119,21 @@ public:
/// Resume the coroutine represented by the handle /// Resume the coroutine represented by the handle
typename result_t::value_t await_resume() noexcept(false) { typename result_t::value_t await_resume() noexcept(false) {
if (result_) { if (result_.is_value()) {
// When the result was resolved return it // When the result was resolved return it
return std::move(result_).get_value(); return std::move(result_).get_value();
} }
assert(result_.is_exception());
#if defined(CONTINUABLE_HAS_EXCEPTIONS) #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 #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(); CTI_DETAIL_TRAP();
#endif // CONTINUABLE_HAS_EXCEPTIONS #endif // CONTINUABLE_HAS_EXCEPTIONS
} }
@ -141,8 +158,7 @@ struct handle_takeover {
handle_ = handle; handle_ = handle;
} }
void await_resume() noexcept { void await_resume() noexcept {}
}
}; };
/// The type which is passed to the compiler that describes the properties /// 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> template <typename Continuable, typename Promise, typename... Args>
struct promise_type struct promise_type
: promise_resolver_base<promise_type<Continuable, Promise, Args...>> { : promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
coroutine_handle<> handle_; coroutine_handle<> handle_;
Promise promise_; Promise promise_;
@ -203,9 +219,15 @@ struct promise_type
void unhandled_exception() noexcept { void unhandled_exception() noexcept {
#if defined(CONTINUABLE_HAS_EXCEPTIONS) #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 #else // CONTINUABLE_HAS_EXCEPTIONS
// Returning error types from coroutines isn't supported // Returning exception types from a coroutine isn't supported
CTI_DETAIL_TRAP(); CTI_DETAIL_TRAP();
#endif // CONTINUABLE_HAS_EXCEPTIONS #endif // CONTINUABLE_HAS_EXCEPTIONS
} }

View File

@ -26,11 +26,11 @@
#include <continuable/detail/features.hpp> #include <continuable/detail/features.hpp>
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE #ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS # ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
#include <exception> # include <exception>
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS # endif // CONTINUABLE_WITH_NO_EXCEPTIONS
#include <tuple> # include <tuple>
/// Resolves the given promise asynchonously /// Resolves the given promise asynchonously
template <typename S> 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); 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 { struct await_exception : std::exception {
char const* what() const noexcept override { 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: // GTest ASSERT_THROW isn't co_await friendly yet:
// clang: 'return statement not allowed in coroutine; did you mean // clang: 'return statement not allowed in coroutine; did you mean
// 'co_return'?' // 'co_return'?'
EXPECT_THROW(co_await supplier().then([] { throw await_exception{}; }), EXPECT_THROW(co_await supplier().then([] {
throw await_exception{};
}),
await_exception); await_exception);
co_return; co_return;
@ -141,6 +143,31 @@ TYPED_TEST(single_dimension_tests, are_awaitable_with_exceptions_from_coro) {
await_exception{}) 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 #endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE