diff --git a/include/continuable/continuable-coroutine.hpp b/include/continuable/continuable-coroutine.hpp index e073735..399d970 100644 --- a/include/continuable/continuable-coroutine.hpp +++ b/include/continuable/continuable-coroutine.hpp @@ -36,15 +36,45 @@ #include #if defined(CONTINUABLE_HAS_EXCEPTIONS) -#include +# include #endif // CONTINUABLE_HAS_EXCEPTIONS #ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE -#include +# include #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(); +/// +/// 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>, FunctionArgs...> { - using promise_type = - cti::detail::awaiting::promise_type, - cti::promise, Args...>; + using promise_type = cti::detail::awaiting::promise_type< + cti::continuable, cti::promise, Args...>; }; } // namespace experimental } // namespace std + /// \endcond #endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE -/// \endcond #endif // CONTINUABLE_COROUTINE_HPP_INCLUDED diff --git a/include/continuable/detail/other/coroutines.hpp b/include/continuable/detail/other/coroutines.hpp index cd1642e..9eb78f6 100644 --- a/include/continuable/detail/other/coroutines.hpp +++ b/include/continuable/detail/other/coroutines.hpp @@ -45,7 +45,7 @@ #include #if defined(CONTINUABLE_HAS_EXCEPTIONS) -#include +# include #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 struct result_from_identity; template @@ -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> { template struct promise_type - : promise_resolver_base> { + : promise_resolver_base> { 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 } diff --git a/test/unit-test/multi/test-continuable-await.cpp b/test/unit-test/multi/test-continuable-await.cpp index d7da021..96df1d9 100644 --- a/test/unit-test/multi/test-continuable-await.cpp +++ b/test/unit-test/multi/test-continuable-await.cpp @@ -26,11 +26,11 @@ #include #ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE -#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS -#include -#endif // CONTINUABLE_WITH_NO_EXCEPTIONS +# ifndef CONTINUABLE_WITH_NO_EXCEPTIONS +# include +# endif // CONTINUABLE_WITH_NO_EXCEPTIONS -#include +# include /// Resolves the given promise asynchonously template @@ -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 +cti::continuable<> resolve_coro_canceled(S&& supplier) { + // Pseudo wait + co_await supplier(); + + try { + co_await cti::make_cancelling_continuable(); + } 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(args)...); + }; + + ASSERT_ASYNC_CANCELLATION(resolve_coro_canceled(supply)) +} + +# endif // CONTINUABLE_WITH_NO_EXCEPTIONS #endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE