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>
|
#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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user