1223 lines
29 KiB
C++
1223 lines
29 KiB
C++
//
|
|
// experimental/impl/coro.hpp
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
|
// (klemens dot morgenstern at gmx dot net)
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
//
|
|
#ifndef ASIO_EXPERIMENTAL_IMPL_CORO_HPP
|
|
#define ASIO_EXPERIMENTAL_IMPL_CORO_HPP
|
|
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
|
# pragma once
|
|
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
|
|
|
#include "asio/detail/config.hpp"
|
|
#include "asio/append.hpp"
|
|
#include "asio/associated_cancellation_slot.hpp"
|
|
#include "asio/bind_allocator.hpp"
|
|
#include "asio/deferred.hpp"
|
|
#include "asio/experimental/detail/coro_completion_handler.hpp"
|
|
#include "asio/detail/push_options.hpp"
|
|
|
|
namespace asio {
|
|
namespace experimental {
|
|
|
|
template <typename Yield, typename Return,
|
|
typename Executor, typename Allocator>
|
|
struct coro;
|
|
|
|
namespace detail {
|
|
|
|
struct coro_cancellation_source
|
|
{
|
|
cancellation_slot slot;
|
|
cancellation_state state;
|
|
bool throw_if_cancelled_ = true;
|
|
|
|
void reset_cancellation_state()
|
|
{
|
|
state = cancellation_state(slot);
|
|
}
|
|
|
|
template <typename Filter>
|
|
void reset_cancellation_state(Filter&& filter)
|
|
{
|
|
state = cancellation_state(slot, static_cast<Filter&&>(filter));
|
|
}
|
|
|
|
template <typename InFilter, typename OutFilter>
|
|
void reset_cancellation_state(InFilter&& in_filter,
|
|
OutFilter&& out_filter)
|
|
{
|
|
state = cancellation_state(slot,
|
|
static_cast<InFilter&&>(in_filter),
|
|
static_cast<OutFilter&&>(out_filter));
|
|
}
|
|
|
|
bool throw_if_cancelled() const
|
|
{
|
|
return throw_if_cancelled_;
|
|
}
|
|
|
|
void throw_if_cancelled(bool value)
|
|
{
|
|
throw_if_cancelled_ = value;
|
|
}
|
|
};
|
|
|
|
template <typename Signature, typename Return,
|
|
typename Executor, typename Allocator>
|
|
struct coro_promise;
|
|
|
|
template <typename T>
|
|
struct is_noexcept : std::false_type
|
|
{
|
|
};
|
|
|
|
template <typename Return, typename... Args>
|
|
struct is_noexcept<Return(Args...)> : std::false_type
|
|
{
|
|
};
|
|
|
|
template <typename Return, typename... Args>
|
|
struct is_noexcept<Return(Args...) noexcept> : std::true_type
|
|
{
|
|
};
|
|
|
|
template <typename T>
|
|
constexpr bool is_noexcept_v = is_noexcept<T>::value;
|
|
|
|
template <typename T>
|
|
struct coro_error;
|
|
|
|
template <>
|
|
struct coro_error<asio::error_code>
|
|
{
|
|
static asio::error_code invalid()
|
|
{
|
|
return asio::error::fault;
|
|
}
|
|
|
|
static asio::error_code cancelled()
|
|
{
|
|
return asio::error::operation_aborted;
|
|
}
|
|
|
|
static asio::error_code interrupted()
|
|
{
|
|
return asio::error::interrupted;
|
|
}
|
|
|
|
static asio::error_code done()
|
|
{
|
|
return asio::error::broken_pipe;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct coro_error<std::exception_ptr>
|
|
{
|
|
static std::exception_ptr invalid()
|
|
{
|
|
return std::make_exception_ptr(
|
|
asio::system_error(
|
|
coro_error<asio::error_code>::invalid()));
|
|
}
|
|
|
|
static std::exception_ptr cancelled()
|
|
{
|
|
return std::make_exception_ptr(
|
|
asio::system_error(
|
|
coro_error<asio::error_code>::cancelled()));
|
|
}
|
|
|
|
static std::exception_ptr interrupted()
|
|
{
|
|
return std::make_exception_ptr(
|
|
asio::system_error(
|
|
coro_error<asio::error_code>::interrupted()));
|
|
}
|
|
|
|
static std::exception_ptr done()
|
|
{
|
|
return std::make_exception_ptr(
|
|
asio::system_error(
|
|
coro_error<asio::error_code>::done()));
|
|
}
|
|
};
|
|
|
|
template <typename T, typename Coroutine >
|
|
struct coro_with_arg
|
|
{
|
|
using coro_t = Coroutine;
|
|
T value;
|
|
coro_t& coro;
|
|
|
|
struct awaitable_t
|
|
{
|
|
T value;
|
|
coro_t& coro;
|
|
|
|
constexpr static bool await_ready() { return false; }
|
|
|
|
template <typename Y, typename R, typename E, typename A>
|
|
auto await_suspend(coroutine_handle<coro_promise<Y, R, E, A>> h)
|
|
-> coroutine_handle<>
|
|
{
|
|
auto& hp = h.promise();
|
|
|
|
if constexpr (!coro_promise<Y, R, E, A>::is_noexcept)
|
|
{
|
|
if ((hp.cancel->state.cancelled() != cancellation_type::none)
|
|
&& hp.cancel->throw_if_cancelled_)
|
|
{
|
|
asio::detail::throw_error(
|
|
asio::error::operation_aborted, "coro-cancelled");
|
|
}
|
|
}
|
|
|
|
if (hp.get_executor() == coro.get_executor())
|
|
{
|
|
coro.coro_->awaited_from = h;
|
|
coro.coro_->reset_error();
|
|
coro.coro_->input_ = std::move(value);
|
|
coro.coro_->cancel = hp.cancel;
|
|
return coro.coro_->get_handle();
|
|
}
|
|
else
|
|
{
|
|
coro.coro_->awaited_from =
|
|
dispatch_coroutine(
|
|
asio::prefer(hp.get_executor(),
|
|
execution::outstanding_work.tracked),
|
|
[h]() mutable { h.resume(); }).handle;
|
|
|
|
coro.coro_->reset_error();
|
|
coro.coro_->input_ = std::move(value);
|
|
|
|
struct cancel_handler
|
|
{
|
|
using src = std::pair<cancellation_signal,
|
|
detail::coro_cancellation_source>;
|
|
|
|
std::shared_ptr<src> st = std::make_shared<src>();
|
|
|
|
cancel_handler(E e, coro_t& coro) : e(e), coro_(coro.coro_)
|
|
{
|
|
st->second.state =
|
|
cancellation_state(st->second.slot = st->first.slot());
|
|
}
|
|
|
|
E e;
|
|
typename coro_t::promise_type* coro_;
|
|
|
|
void operator()(cancellation_type ct)
|
|
{
|
|
asio::dispatch(e, [ct, st = st]() mutable
|
|
{
|
|
auto & [sig, state] = *st;
|
|
sig.emit(ct);
|
|
});
|
|
}
|
|
};
|
|
|
|
if (hp.cancel->state.slot().is_connected())
|
|
{
|
|
hp.cancel->state.slot().template emplace<cancel_handler>(
|
|
coro.get_executor(), coro);
|
|
}
|
|
|
|
auto hh = detail::coroutine_handle<
|
|
typename coro_t::promise_type>::from_promise(*coro.coro_);
|
|
|
|
return dispatch_coroutine(
|
|
coro.coro_->get_executor(), [hh]() mutable { hh.resume(); }).handle;
|
|
}
|
|
}
|
|
|
|
auto await_resume() -> typename coro_t::result_type
|
|
{
|
|
coro.coro_->cancel = nullptr;
|
|
coro.coro_->rethrow_if();
|
|
return std::move(coro.coro_->result_);
|
|
}
|
|
};
|
|
|
|
template <typename CompletionToken>
|
|
auto async_resume(CompletionToken&& token) &&
|
|
{
|
|
return coro.async_resume(std::move(value),
|
|
std::forward<CompletionToken>(token));
|
|
}
|
|
|
|
auto operator co_await() &&
|
|
{
|
|
return awaitable_t{std::move(value), coro};
|
|
}
|
|
};
|
|
|
|
template <bool IsNoexcept>
|
|
struct coro_promise_error;
|
|
|
|
template <>
|
|
struct coro_promise_error<false>
|
|
{
|
|
std::exception_ptr error_;
|
|
|
|
void reset_error()
|
|
{
|
|
error_ = std::exception_ptr{};
|
|
}
|
|
|
|
void unhandled_exception()
|
|
{
|
|
error_ = std::current_exception();
|
|
}
|
|
|
|
void rethrow_if()
|
|
{
|
|
if (error_)
|
|
std::rethrow_exception(error_);
|
|
}
|
|
};
|
|
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic push
|
|
# if defined(__clang__)
|
|
# pragma GCC diagnostic ignored "-Wexceptions"
|
|
# else
|
|
# pragma GCC diagnostic ignored "-Wterminate"
|
|
# endif
|
|
#elif defined(_MSC_VER)
|
|
# pragma warning(push)
|
|
# pragma warning (disable:4297)
|
|
#endif
|
|
|
|
template <>
|
|
struct coro_promise_error<true>
|
|
{
|
|
void reset_error()
|
|
{
|
|
}
|
|
|
|
void unhandled_exception() noexcept
|
|
{
|
|
throw;
|
|
}
|
|
|
|
void rethrow_if()
|
|
{
|
|
}
|
|
};
|
|
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic pop
|
|
#elif defined(_MSC_VER)
|
|
# pragma warning(pop)
|
|
#endif
|
|
|
|
template <typename T = void>
|
|
struct yield_input
|
|
{
|
|
T& value;
|
|
coroutine_handle<> awaited_from{noop_coroutine()};
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return false;
|
|
}
|
|
|
|
template <typename U>
|
|
coroutine_handle<> await_suspend(coroutine_handle<U>) noexcept
|
|
{
|
|
return std::exchange(awaited_from, noop_coroutine());
|
|
}
|
|
|
|
T await_resume() const noexcept
|
|
{
|
|
return std::move(value);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct yield_input<void>
|
|
{
|
|
coroutine_handle<> awaited_from{noop_coroutine()};
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto await_suspend(coroutine_handle<>) noexcept
|
|
{
|
|
return std::exchange(awaited_from, noop_coroutine());
|
|
}
|
|
|
|
constexpr void await_resume() const noexcept
|
|
{
|
|
}
|
|
};
|
|
|
|
struct coro_awaited_from
|
|
{
|
|
coroutine_handle<> awaited_from{noop_coroutine()};
|
|
|
|
auto final_suspend() noexcept
|
|
{
|
|
struct suspendor
|
|
{
|
|
coroutine_handle<> awaited_from;
|
|
|
|
constexpr static bool await_ready() noexcept
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto await_suspend(coroutine_handle<>) noexcept
|
|
{
|
|
return std::exchange(awaited_from, noop_coroutine());
|
|
}
|
|
|
|
constexpr static void await_resume() noexcept
|
|
{
|
|
}
|
|
};
|
|
|
|
return suspendor{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
~coro_awaited_from()
|
|
{
|
|
awaited_from.resume();
|
|
}//must be on the right executor
|
|
};
|
|
|
|
template <typename Yield, typename Input, typename Return>
|
|
struct coro_promise_exchange : coro_awaited_from
|
|
{
|
|
using result_type = coro_result_t<Yield, Return>;
|
|
|
|
result_type result_;
|
|
Input input_;
|
|
|
|
auto yield_value(Yield&& y)
|
|
{
|
|
result_ = std::move(y);
|
|
return yield_input<Input>{std::move(input_),
|
|
std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
auto yield_value(const Yield& y)
|
|
{
|
|
result_ = y;
|
|
return yield_input<Input>{std::move(input_),
|
|
std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
void return_value(const Return& r)
|
|
{
|
|
result_ = r;
|
|
}
|
|
|
|
void return_value(Return&& r)
|
|
{
|
|
result_ = std::move(r);
|
|
}
|
|
};
|
|
|
|
template <typename YieldReturn>
|
|
struct coro_promise_exchange<YieldReturn, void, YieldReturn> : coro_awaited_from
|
|
{
|
|
using result_type = coro_result_t<YieldReturn, YieldReturn>;
|
|
|
|
result_type result_;
|
|
|
|
auto yield_value(const YieldReturn& y)
|
|
{
|
|
result_ = y;
|
|
return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
auto yield_value(YieldReturn&& y)
|
|
{
|
|
result_ = std::move(y);
|
|
return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
void return_value(const YieldReturn& r)
|
|
{
|
|
result_ = r;
|
|
}
|
|
|
|
void return_value(YieldReturn&& r)
|
|
{
|
|
result_ = std::move(r);
|
|
}
|
|
};
|
|
|
|
template <typename Yield, typename Return>
|
|
struct coro_promise_exchange<Yield, void, Return> : coro_awaited_from
|
|
{
|
|
using result_type = coro_result_t<Yield, Return>;
|
|
|
|
result_type result_;
|
|
|
|
auto yield_value(const Yield& y)
|
|
{
|
|
result_.template emplace<0>(y);
|
|
return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
auto yield_value(Yield&& y)
|
|
{
|
|
result_.template emplace<0>(std::move(y));
|
|
return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
void return_value(const Return& r)
|
|
{
|
|
result_.template emplace<1>(r);
|
|
}
|
|
|
|
void return_value(Return&& r)
|
|
{
|
|
result_.template emplace<1>(std::move(r));
|
|
}
|
|
};
|
|
|
|
template <typename Yield, typename Input>
|
|
struct coro_promise_exchange<Yield, Input, void> : coro_awaited_from
|
|
{
|
|
using result_type = coro_result_t<Yield, void>;
|
|
|
|
result_type result_;
|
|
Input input_;
|
|
|
|
auto yield_value(Yield&& y)
|
|
{
|
|
result_ = std::move(y);
|
|
return yield_input<Input>{input_,
|
|
std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
auto yield_value(const Yield& y)
|
|
{
|
|
result_ = y;
|
|
return yield_input<Input>{input_,
|
|
std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
void return_void()
|
|
{
|
|
result_.reset();
|
|
}
|
|
};
|
|
|
|
template <typename Return>
|
|
struct coro_promise_exchange<void, void, Return> : coro_awaited_from
|
|
{
|
|
using result_type = coro_result_t<void, Return>;
|
|
|
|
result_type result_;
|
|
|
|
void yield_value();
|
|
|
|
void return_value(const Return& r)
|
|
{
|
|
result_ = r;
|
|
}
|
|
|
|
void return_value(Return&& r)
|
|
{
|
|
result_ = std::move(r);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct coro_promise_exchange<void, void, void> : coro_awaited_from
|
|
{
|
|
void return_void() {}
|
|
|
|
void yield_value();
|
|
};
|
|
|
|
template <typename Yield>
|
|
struct coro_promise_exchange<Yield, void, void> : coro_awaited_from
|
|
{
|
|
using result_type = coro_result_t<Yield, void>;
|
|
|
|
result_type result_;
|
|
|
|
auto yield_value(const Yield& y)
|
|
{
|
|
result_ = y;
|
|
return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
auto yield_value(Yield&& y)
|
|
{
|
|
result_ = std::move(y);
|
|
return yield_input<void>{std::exchange(awaited_from, noop_coroutine())};
|
|
}
|
|
|
|
void return_void()
|
|
{
|
|
result_.reset();
|
|
}
|
|
};
|
|
|
|
template <typename Yield, typename Return,
|
|
typename Executor, typename Allocator>
|
|
struct coro_promise final :
|
|
coro_promise_allocator<Allocator>,
|
|
coro_promise_error<coro_traits<Yield, Return, Executor>::is_noexcept>,
|
|
coro_promise_exchange<
|
|
typename coro_traits<Yield, Return, Executor>::yield_type,
|
|
typename coro_traits<Yield, Return, Executor>::input_type,
|
|
typename coro_traits<Yield, Return, Executor>::return_type>
|
|
{
|
|
using coro_type = coro<Yield, Return, Executor, Allocator>;
|
|
|
|
auto handle()
|
|
{
|
|
return coroutine_handle<coro_promise>::from_promise(this);
|
|
}
|
|
|
|
using executor_type = Executor;
|
|
|
|
executor_type executor_;
|
|
|
|
std::optional<coro_cancellation_source> cancel_source;
|
|
coro_cancellation_source * cancel;
|
|
|
|
using cancellation_slot_type = asio::cancellation_slot;
|
|
|
|
cancellation_slot_type get_cancellation_slot() const noexcept
|
|
{
|
|
return cancel ? cancel->slot : cancellation_slot_type{};
|
|
}
|
|
|
|
using allocator_type =
|
|
typename std::allocator_traits<associated_allocator_t<Executor>>::
|
|
template rebind_alloc<std::byte>;
|
|
using traits = coro_traits<Yield, Return, Executor>;
|
|
|
|
using input_type = typename traits::input_type;
|
|
using yield_type = typename traits::yield_type;
|
|
using return_type = typename traits::return_type;
|
|
using error_type = typename traits::error_type;
|
|
using result_type = typename traits::result_type;
|
|
constexpr static bool is_noexcept = traits::is_noexcept;
|
|
|
|
auto get_executor() const -> Executor
|
|
{
|
|
return executor_;
|
|
}
|
|
|
|
auto get_handle()
|
|
{
|
|
return coroutine_handle<coro_promise>::from_promise(*this);
|
|
}
|
|
|
|
template <typename... Args>
|
|
coro_promise(Executor executor, Args&&... args) noexcept
|
|
: coro_promise_allocator<Allocator>(
|
|
executor, std::forward<Args>(args)...),
|
|
executor_(std::move(executor))
|
|
{
|
|
}
|
|
|
|
template <typename First, typename... Args>
|
|
coro_promise(First&& f, Executor executor, Args&&... args) noexcept
|
|
: coro_promise_allocator<Allocator>(
|
|
f, executor, std::forward<Args>(args)...),
|
|
executor_(std::move(executor))
|
|
{
|
|
}
|
|
|
|
template <typename First, detail::execution_context Context, typename... Args>
|
|
coro_promise(First&& f, Context&& ctx, Args&&... args) noexcept
|
|
: coro_promise_allocator<Allocator>(
|
|
f, ctx, std::forward<Args>(args)...),
|
|
executor_(ctx.get_executor())
|
|
{
|
|
}
|
|
|
|
template <detail::execution_context Context, typename... Args>
|
|
coro_promise(Context&& ctx, Args&&... args) noexcept
|
|
: coro_promise_allocator<Allocator>(
|
|
ctx, std::forward<Args>(args)...),
|
|
executor_(ctx.get_executor())
|
|
{
|
|
}
|
|
|
|
auto get_return_object()
|
|
{
|
|
return coro<Yield, Return, Executor, Allocator>{this};
|
|
}
|
|
|
|
auto initial_suspend() noexcept
|
|
{
|
|
return suspend_always{};
|
|
}
|
|
|
|
using coro_promise_exchange<
|
|
typename coro_traits<Yield, Return, Executor>::yield_type,
|
|
typename coro_traits<Yield, Return, Executor>::input_type,
|
|
typename coro_traits<Yield, Return, Executor>::return_type>::yield_value;
|
|
|
|
auto await_transform(this_coro::executor_t) const
|
|
{
|
|
struct exec_helper
|
|
{
|
|
const executor_type& value;
|
|
|
|
constexpr static bool await_ready() noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
constexpr static void await_suspend(coroutine_handle<>) noexcept
|
|
{
|
|
}
|
|
|
|
executor_type await_resume() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
};
|
|
|
|
return exec_helper{executor_};
|
|
}
|
|
|
|
auto await_transform(this_coro::cancellation_state_t) const
|
|
{
|
|
struct exec_helper
|
|
{
|
|
const asio::cancellation_state& value;
|
|
|
|
constexpr static bool await_ready() noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
constexpr static void await_suspend(coroutine_handle<>) noexcept
|
|
{
|
|
}
|
|
|
|
asio::cancellation_state await_resume() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
};
|
|
assert(cancel);
|
|
return exec_helper{cancel->state};
|
|
}
|
|
|
|
// This await transformation resets the associated cancellation state.
|
|
auto await_transform(this_coro::reset_cancellation_state_0_t) noexcept
|
|
{
|
|
struct result
|
|
{
|
|
detail::coro_cancellation_source * src_;
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void await_suspend(coroutine_handle<void>) noexcept
|
|
{
|
|
}
|
|
|
|
auto await_resume() const
|
|
{
|
|
return src_->reset_cancellation_state();
|
|
}
|
|
};
|
|
|
|
return result{cancel};
|
|
}
|
|
|
|
// This await transformation resets the associated cancellation state.
|
|
template <typename Filter>
|
|
auto await_transform(
|
|
this_coro::reset_cancellation_state_1_t<Filter> reset) noexcept
|
|
{
|
|
struct result
|
|
{
|
|
detail::coro_cancellation_source* src_;
|
|
Filter filter_;
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void await_suspend(coroutine_handle<void>) noexcept
|
|
{
|
|
}
|
|
|
|
auto await_resume()
|
|
{
|
|
return src_->reset_cancellation_state(
|
|
static_cast<Filter&&>(filter_));
|
|
}
|
|
};
|
|
|
|
return result{cancel, static_cast<Filter&&>(reset.filter)};
|
|
}
|
|
|
|
// This await transformation resets the associated cancellation state.
|
|
template <typename InFilter, typename OutFilter>
|
|
auto await_transform(
|
|
this_coro::reset_cancellation_state_2_t<InFilter, OutFilter> reset)
|
|
noexcept
|
|
{
|
|
struct result
|
|
{
|
|
detail::coro_cancellation_source* src_;
|
|
InFilter in_filter_;
|
|
OutFilter out_filter_;
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void await_suspend(coroutine_handle<void>) noexcept
|
|
{
|
|
}
|
|
|
|
auto await_resume()
|
|
{
|
|
return src_->reset_cancellation_state(
|
|
static_cast<InFilter&&>(in_filter_),
|
|
static_cast<OutFilter&&>(out_filter_));
|
|
}
|
|
};
|
|
|
|
return result{cancel,
|
|
static_cast<InFilter&&>(reset.in_filter),
|
|
static_cast<OutFilter&&>(reset.out_filter)};
|
|
}
|
|
|
|
// This await transformation determines whether cancellation is propagated as
|
|
// an exception.
|
|
auto await_transform(this_coro::throw_if_cancelled_0_t) noexcept
|
|
requires (!is_noexcept)
|
|
{
|
|
struct result
|
|
{
|
|
detail::coro_cancellation_source* src_;
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void await_suspend(coroutine_handle<void>) noexcept
|
|
{
|
|
}
|
|
|
|
auto await_resume()
|
|
{
|
|
return src_->throw_if_cancelled();
|
|
}
|
|
};
|
|
|
|
return result{cancel};
|
|
}
|
|
|
|
// This await transformation sets whether cancellation is propagated as an
|
|
// exception.
|
|
auto await_transform(
|
|
this_coro::throw_if_cancelled_1_t throw_if_cancelled) noexcept
|
|
requires (!is_noexcept)
|
|
{
|
|
struct result
|
|
{
|
|
detail::coro_cancellation_source* src_;
|
|
bool value_;
|
|
|
|
bool await_ready() const noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void await_suspend(coroutine_handle<void>) noexcept
|
|
{
|
|
}
|
|
|
|
auto await_resume()
|
|
{
|
|
src_->throw_if_cancelled(value_);
|
|
}
|
|
};
|
|
|
|
return result{cancel, throw_if_cancelled.value};
|
|
}
|
|
|
|
template <typename Yield_, typename Return_,
|
|
typename Executor_, typename Allocator_>
|
|
auto await_transform(coro<Yield_, Return_, Executor_, Allocator_>& kr)
|
|
-> decltype(auto)
|
|
{
|
|
return kr;
|
|
}
|
|
|
|
template <typename Yield_, typename Return_,
|
|
typename Executor_, typename Allocator_>
|
|
auto await_transform(coro<Yield_, Return_, Executor_, Allocator_>&& kr)
|
|
{
|
|
return std::move(kr);
|
|
}
|
|
|
|
template <typename T_, typename Coroutine >
|
|
auto await_transform(coro_with_arg<T_, Coroutine>&& kr) -> decltype(auto)
|
|
{
|
|
return std::move(kr);
|
|
}
|
|
|
|
template <typename T_>
|
|
requires requires(T_ t) {{ t.async_wait(deferred) }; }
|
|
auto await_transform(T_& t) -> decltype(auto)
|
|
{
|
|
return await_transform(t.async_wait(deferred));
|
|
}
|
|
|
|
template <typename Op>
|
|
auto await_transform(Op&& op,
|
|
constraint_t<is_async_operation<Op>::value> = 0)
|
|
{
|
|
if ((cancel->state.cancelled() != cancellation_type::none)
|
|
&& cancel->throw_if_cancelled_)
|
|
{
|
|
asio::detail::throw_error(
|
|
asio::error::operation_aborted, "coro-cancelled");
|
|
}
|
|
using signature = completion_signature_of_t<Op>;
|
|
using result_type = detail::coro_completion_handler_type_t<signature>;
|
|
using handler_type =
|
|
typename detail::coro_completion_handler_type<signature>::template
|
|
completion_handler<coro_promise>;
|
|
|
|
struct aw_t
|
|
{
|
|
Op op;
|
|
std::optional<result_type> result;
|
|
|
|
constexpr static bool await_ready()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void await_suspend(coroutine_handle<coro_promise> h)
|
|
{
|
|
std::move(op)(handler_type{h, result});
|
|
}
|
|
|
|
auto await_resume()
|
|
{
|
|
if constexpr (is_noexcept)
|
|
{
|
|
if constexpr (std::tuple_size_v<result_type> == 0u)
|
|
return;
|
|
else if constexpr (std::tuple_size_v<result_type> == 1u)
|
|
return std::get<0>(std::move(result).value());
|
|
else
|
|
return std::move(result).value();
|
|
}
|
|
else
|
|
return detail::coro_interpret_result(std::move(result).value());
|
|
}
|
|
};
|
|
|
|
return aw_t{std::move(op), {}};
|
|
}
|
|
};
|
|
|
|
} // namespace detail
|
|
|
|
template <typename Yield, typename Return,
|
|
typename Executor, typename Allocator>
|
|
struct coro<Yield, Return, Executor, Allocator>::awaitable_t
|
|
{
|
|
coro& coro_;
|
|
|
|
constexpr static bool await_ready() { return false; }
|
|
|
|
template <typename Y, typename R, typename E, typename A>
|
|
auto await_suspend(
|
|
detail::coroutine_handle<detail::coro_promise<Y, R, E, A>> h)
|
|
-> detail::coroutine_handle<>
|
|
{
|
|
auto& hp = h.promise();
|
|
|
|
if constexpr (!detail::coro_promise<Y, R, E, A>::is_noexcept)
|
|
{
|
|
if ((hp.cancel->state.cancelled() != cancellation_type::none)
|
|
&& hp.cancel->throw_if_cancelled_)
|
|
{
|
|
asio::detail::throw_error(
|
|
asio::error::operation_aborted, "coro-cancelled");
|
|
}
|
|
}
|
|
|
|
if (hp.get_executor() == coro_.get_executor())
|
|
{
|
|
coro_.coro_->awaited_from = h;
|
|
coro_.coro_->cancel = hp.cancel;
|
|
coro_.coro_->reset_error();
|
|
|
|
return coro_.coro_->get_handle();
|
|
}
|
|
else
|
|
{
|
|
coro_.coro_->awaited_from = detail::dispatch_coroutine(
|
|
asio::prefer(hp.get_executor(),
|
|
execution::outstanding_work.tracked),
|
|
[h]() mutable
|
|
{
|
|
h.resume();
|
|
}).handle;
|
|
|
|
coro_.coro_->reset_error();
|
|
|
|
struct cancel_handler
|
|
{
|
|
std::shared_ptr<std::pair<cancellation_signal,
|
|
detail::coro_cancellation_source>> st = std::make_shared<
|
|
std::pair<cancellation_signal, detail::coro_cancellation_source>>();
|
|
|
|
cancel_handler(E e, coro& coro) : e(e), coro_(coro.coro_)
|
|
{
|
|
st->second.state = cancellation_state(
|
|
st->second.slot = st->first.slot());
|
|
}
|
|
|
|
E e;
|
|
typename coro::promise_type* coro_;
|
|
|
|
void operator()(cancellation_type ct)
|
|
{
|
|
asio::dispatch(e,
|
|
[ct, st = st]() mutable
|
|
{
|
|
auto & [sig, state] = *st;
|
|
sig.emit(ct);
|
|
});
|
|
}
|
|
};
|
|
|
|
if (hp.cancel->state.slot().is_connected())
|
|
{
|
|
hp.cancel->state.slot().template emplace<cancel_handler>(
|
|
coro_.get_executor(), coro_);
|
|
}
|
|
|
|
auto hh = detail::coroutine_handle<
|
|
detail::coro_promise<Yield, Return, Executor, Allocator>>::from_promise(
|
|
*coro_.coro_);
|
|
|
|
return detail::dispatch_coroutine(
|
|
coro_.coro_->get_executor(),
|
|
[hh]() mutable { hh.resume(); }).handle;
|
|
}
|
|
}
|
|
|
|
auto await_resume() -> result_type
|
|
{
|
|
coro_.coro_->cancel = nullptr;
|
|
coro_.coro_->rethrow_if();
|
|
if constexpr (!std::is_void_v<result_type>)
|
|
return std::move(coro_.coro_->result_);
|
|
}
|
|
};
|
|
|
|
template <typename Yield, typename Return,
|
|
typename Executor, typename Allocator>
|
|
struct coro<Yield, Return, Executor, Allocator>::initiate_async_resume
|
|
{
|
|
typedef Executor executor_type;
|
|
typedef Allocator allocator_type;
|
|
typedef asio::cancellation_slot cancellation_slot_type;
|
|
|
|
explicit initiate_async_resume(coro* self)
|
|
: coro_(self->coro_)
|
|
{
|
|
}
|
|
|
|
executor_type get_executor() const noexcept
|
|
{
|
|
return coro_->get_executor();
|
|
}
|
|
|
|
allocator_type get_allocator() const noexcept
|
|
{
|
|
return coro_->get_allocator();
|
|
}
|
|
|
|
template <typename E, typename WaitHandler>
|
|
auto handle(E exec, WaitHandler&& handler,
|
|
std::true_type /* error is noexcept */,
|
|
std::true_type /* result is void */) //noexcept
|
|
{
|
|
return [this, the_coro = coro_,
|
|
h = std::forward<WaitHandler>(handler),
|
|
exec = std::move(exec)]() mutable
|
|
{
|
|
assert(the_coro);
|
|
|
|
auto ch = detail::coroutine_handle<promise_type>::from_promise(*the_coro);
|
|
assert(ch && !ch.done());
|
|
|
|
the_coro->awaited_from = post_coroutine(std::move(exec), std::move(h));
|
|
the_coro->reset_error();
|
|
ch.resume();
|
|
};
|
|
}
|
|
|
|
template <typename E, typename WaitHandler>
|
|
requires (!std::is_void_v<result_type>)
|
|
auto handle(E exec, WaitHandler&& handler,
|
|
std::true_type /* error is noexcept */,
|
|
std::false_type /* result is void */) //noexcept
|
|
{
|
|
return [the_coro = coro_,
|
|
h = std::forward<WaitHandler>(handler),
|
|
exec = std::move(exec)]() mutable
|
|
{
|
|
assert(the_coro);
|
|
|
|
auto ch = detail::coroutine_handle<promise_type>::from_promise(*the_coro);
|
|
assert(ch && !ch.done());
|
|
|
|
the_coro->awaited_from = detail::post_coroutine(
|
|
exec, std::move(h), the_coro->result_).handle;
|
|
the_coro->reset_error();
|
|
ch.resume();
|
|
};
|
|
}
|
|
|
|
template <typename E, typename WaitHandler>
|
|
auto handle(E exec, WaitHandler&& handler,
|
|
std::false_type /* error is noexcept */,
|
|
std::true_type /* result is void */)
|
|
{
|
|
return [the_coro = coro_,
|
|
h = std::forward<WaitHandler>(handler),
|
|
exec = std::move(exec)]() mutable
|
|
{
|
|
if (!the_coro)
|
|
return asio::post(exec,
|
|
asio::append(std::move(h),
|
|
detail::coro_error<error_type>::invalid()));
|
|
|
|
auto ch = detail::coroutine_handle<promise_type>::from_promise(*the_coro);
|
|
if (!ch)
|
|
return asio::post(exec,
|
|
asio::append(std::move(h),
|
|
detail::coro_error<error_type>::invalid()));
|
|
else if (ch.done())
|
|
return asio::post(exec,
|
|
asio::append(std::move(h),
|
|
detail::coro_error<error_type>::done()));
|
|
else
|
|
{
|
|
the_coro->awaited_from = detail::post_coroutine(
|
|
exec, std::move(h), the_coro->error_).handle;
|
|
the_coro->reset_error();
|
|
ch.resume();
|
|
}
|
|
};
|
|
}
|
|
|
|
template <typename E, typename WaitHandler>
|
|
auto handle(E exec, WaitHandler&& handler,
|
|
std::false_type /* error is noexcept */,
|
|
std::false_type /* result is void */)
|
|
{
|
|
return [the_coro = coro_,
|
|
h = std::forward<WaitHandler>(handler),
|
|
exec = std::move(exec)]() mutable
|
|
{
|
|
if (!the_coro)
|
|
return asio::post(exec,
|
|
asio::append(std::move(h),
|
|
detail::coro_error<error_type>::invalid(), result_type{}));
|
|
|
|
auto ch =
|
|
detail::coroutine_handle<promise_type>::from_promise(*the_coro);
|
|
if (!ch)
|
|
return asio::post(exec,
|
|
asio::append(std::move(h),
|
|
detail::coro_error<error_type>::invalid(), result_type{}));
|
|
else if (ch.done())
|
|
return asio::post(exec,
|
|
asio::append(std::move(h),
|
|
detail::coro_error<error_type>::done(), result_type{}));
|
|
else
|
|
{
|
|
the_coro->awaited_from = detail::post_coroutine(
|
|
exec, std::move(h), the_coro->error_, the_coro->result_).handle;
|
|
the_coro->reset_error();
|
|
ch.resume();
|
|
}
|
|
};
|
|
}
|
|
|
|
template <typename WaitHandler>
|
|
void operator()(WaitHandler&& handler)
|
|
{
|
|
const auto exec = asio::prefer(
|
|
get_associated_executor(handler, get_executor()),
|
|
execution::outstanding_work.tracked);
|
|
|
|
coro_->cancel = &coro_->cancel_source.emplace();
|
|
coro_->cancel->state = cancellation_state(
|
|
coro_->cancel->slot = get_associated_cancellation_slot(handler));
|
|
asio::dispatch(get_executor(),
|
|
handle(exec, std::forward<WaitHandler>(handler),
|
|
std::integral_constant<bool, is_noexcept>{},
|
|
std::is_void<result_type>{}));
|
|
}
|
|
|
|
template <typename WaitHandler, typename Input>
|
|
void operator()(WaitHandler&& handler, Input&& input)
|
|
{
|
|
const auto exec = asio::prefer(
|
|
get_associated_executor(handler, get_executor()),
|
|
execution::outstanding_work.tracked);
|
|
|
|
coro_->cancel = &coro_->cancel_source.emplace();
|
|
coro_->cancel->state = cancellation_state(
|
|
coro_->cancel->slot = get_associated_cancellation_slot(handler));
|
|
asio::dispatch(get_executor(),
|
|
[h = handle(exec, std::forward<WaitHandler>(handler),
|
|
std::integral_constant<bool, is_noexcept>{},
|
|
std::is_void<result_type>{}),
|
|
in = std::forward<Input>(input), the_coro = coro_]() mutable
|
|
{
|
|
the_coro->input_ = std::move(in);
|
|
std::move(h)();
|
|
});
|
|
}
|
|
|
|
private:
|
|
typename coro::promise_type* coro_;
|
|
};
|
|
|
|
} // namespace experimental
|
|
} // namespace asio
|
|
|
|
#include "asio/detail/pop_options.hpp"
|
|
|
|
#endif // ASIO_EXPERIMENTAL_IMPL_CORO_HPP
|