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

View File

@ -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>
@ -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
@ -203,9 +219,15 @@ struct promise_type
void unhandled_exception() noexcept {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
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
}

View File

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