mirror of
https://github.com/vdemydiuk/mtapi.git
synced 2026-08-16 12:18:09 +00:00
Added thirdparty: boost library
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_AWAIT_RESULT_HELPER_HPP
|
||||
#define BOOST_COBALT_DETAIL_AWAIT_RESULT_HELPER_HPP
|
||||
|
||||
#include <boost/cobalt/concepts.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
template<awaitable_type T>
|
||||
auto co_await_result_helper() -> decltype(std::declval<T&>());
|
||||
|
||||
template<typename T>
|
||||
auto co_await_result_helper() -> decltype(std::declval<T>().operator co_await());
|
||||
|
||||
template<typename T>
|
||||
auto co_await_result_helper() -> decltype(operator co_await(std::declval<T>()));
|
||||
|
||||
template<typename T>
|
||||
using co_awaitable_type = decltype(co_await_result_helper<T>());
|
||||
|
||||
template<typename T>
|
||||
using co_await_result_t = decltype(co_await_result_helper<T>().await_resume());
|
||||
|
||||
template<awaitable_type T>
|
||||
T&& get_awaitable_type(T && t) { return std::forward<T>(t);}
|
||||
|
||||
template<typename T>
|
||||
requires (requires (T && t) {{operator co_await(std::forward<T>(t))} -> awaitable_type;} )
|
||||
decltype(auto) get_awaitable_type(T && t) { return operator co_await(std::forward<T>(t));}
|
||||
|
||||
template<typename T>
|
||||
requires (requires (T && t) {{std::forward<T>(t).operator co_await()} -> awaitable_type;} )
|
||||
decltype(auto) get_awaitable_type(T && t) { return std::forward<T>(t).operator co_await();}
|
||||
|
||||
template<typename T>
|
||||
struct awaitable_type_getter
|
||||
{
|
||||
using type = co_awaitable_type<T&&>;
|
||||
std::decay_t<T> & ref;
|
||||
|
||||
template<typename U>
|
||||
awaitable_type_getter(U && ref) : ref(ref) {}
|
||||
|
||||
operator type ()
|
||||
{
|
||||
if constexpr (std::is_lvalue_reference_v<T>)
|
||||
return get_awaitable_type(ref);
|
||||
else
|
||||
return get_awaitable_type(std::move(ref));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<awaitable_type T>
|
||||
struct awaitable_type_getter<T>
|
||||
{
|
||||
using type = T&&;
|
||||
std::decay_t<T> & ref;
|
||||
|
||||
template<typename U>
|
||||
awaitable_type_getter(U && ref) : ref(ref) {}
|
||||
|
||||
operator type ()
|
||||
{
|
||||
if constexpr (std::is_lvalue_reference_v<T>)
|
||||
return ref;
|
||||
else
|
||||
return std::move(ref);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_AWAIT_RESULT_HELPER_HPP
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_DETACHED_HPP
|
||||
#define BOOST_COBALT_DETAIL_DETACHED_HPP
|
||||
|
||||
#include <boost/cobalt/detail/exception.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/wrapper.hpp>
|
||||
#include <boost/cobalt/detail/this_thread.hpp>
|
||||
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
|
||||
|
||||
|
||||
#include <boost/core/exchange.hpp>
|
||||
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
struct detached;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct detached_promise
|
||||
: promise_memory_resource_base,
|
||||
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
||||
promise_throw_if_cancelled_base,
|
||||
enable_awaitables<detached_promise>,
|
||||
enable_await_allocator<detached_promise>,
|
||||
enable_await_executor<detached_promise>
|
||||
{
|
||||
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
||||
using promise_throw_if_cancelled_base::await_transform;
|
||||
using enable_awaitables<detached_promise>::await_transform;
|
||||
using enable_await_allocator<detached_promise>::await_transform;
|
||||
using enable_await_executor<detached_promise>::await_transform;
|
||||
|
||||
[[nodiscard]] detached get_return_object();
|
||||
|
||||
std::suspend_never await_transform(
|
||||
cobalt::this_coro::reset_cancellation_source_t<asio::cancellation_slot> reset) noexcept
|
||||
{
|
||||
this->reset_cancellation_source(reset.source);
|
||||
return {};
|
||||
}
|
||||
|
||||
using executor_type = executor;
|
||||
executor_type exec;
|
||||
const executor_type & get_executor() const {return exec;}
|
||||
|
||||
template<typename ... Args>
|
||||
detached_promise(Args & ...args)
|
||||
:
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
promise_memory_resource_base(detail::get_memory_resource_from_args(args...)),
|
||||
#endif
|
||||
exec{detail::get_executor_from_args(args...)}
|
||||
{
|
||||
}
|
||||
|
||||
std::suspend_never initial_suspend() {return {};}
|
||||
std::suspend_never final_suspend() noexcept {return {};}
|
||||
|
||||
void return_void() {}
|
||||
void unhandled_exception()
|
||||
{
|
||||
throw ;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_DETACHED_HPP
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_EXCEPTION_HPP
|
||||
#define BOOST_COBALT_DETAIL_EXCEPTION_HPP
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/cobalt/config.hpp>
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
BOOST_COBALT_DECL std::exception_ptr moved_from_exception();
|
||||
BOOST_COBALT_DECL std::exception_ptr detached_exception();
|
||||
BOOST_COBALT_DECL std::exception_ptr completed_unexpected();
|
||||
BOOST_COBALT_DECL std::exception_ptr wait_not_ready();
|
||||
BOOST_COBALT_DECL std::exception_ptr already_awaited();
|
||||
BOOST_COBALT_DECL std::exception_ptr allocation_failed();
|
||||
|
||||
template<typename >
|
||||
std::exception_ptr wait_not_ready() { return boost::cobalt::detail::wait_not_ready();}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_EXCEPTION_HPP
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
// Copyright (c) 2023 Klemens D. Morgenstern
|
||||
//
|
||||
// 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 BOOST_COBALT_DETAIL_FORK_HPP
|
||||
#define BOOST_COBALT_DETAIL_FORK_HPP
|
||||
|
||||
#include <boost/cobalt/config.hpp>
|
||||
#include <boost/cobalt/detail/await_result_helper.hpp>
|
||||
#include <boost/cobalt/detail/util.hpp>
|
||||
#include <boost/cobalt/this_thread.hpp>
|
||||
#include <boost/cobalt/unique_handle.hpp>
|
||||
#if defined(BOOST_COBALT_NO_PMR)
|
||||
#include <boost/cobalt/detail/monotonic_resource.hpp>
|
||||
#endif
|
||||
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
|
||||
#include <coroutine>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct fork
|
||||
{
|
||||
fork() = default;
|
||||
struct shared_state
|
||||
{
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::monotonic_buffer_resource resource;
|
||||
template<typename ... Args>
|
||||
shared_state(Args && ... args)
|
||||
: resource(std::forward<Args>(args)...,
|
||||
this_thread::get_default_resource())
|
||||
{
|
||||
}
|
||||
#else
|
||||
detail::monotonic_resource resource;
|
||||
template<typename ... Args>
|
||||
shared_state(Args && ... args)
|
||||
: resource(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
// the coro awaiting the fork statement, e.g. awaiting race
|
||||
unique_handle<void> coro;
|
||||
std::size_t use_count = 0u;
|
||||
|
||||
friend void intrusive_ptr_add_ref(shared_state * st) {st->use_count++;}
|
||||
friend void intrusive_ptr_release(shared_state * st)
|
||||
{
|
||||
if (st->use_count-- == 1u)
|
||||
st->coro.reset();
|
||||
}
|
||||
|
||||
bool outstanding_work() {return use_count != 0u;}
|
||||
|
||||
const executor * exec = nullptr;
|
||||
bool wired_up() {return exec != nullptr;}
|
||||
|
||||
using executor_type = executor;
|
||||
const executor_type & get_executor() const
|
||||
{
|
||||
BOOST_ASSERT(exec != nullptr);
|
||||
return *exec;
|
||||
}
|
||||
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
boost::source_location loc;
|
||||
#endif
|
||||
};
|
||||
|
||||
template<typename std::size_t BufferSize>
|
||||
struct static_shared_state : private std::array<char, BufferSize>, shared_state
|
||||
{
|
||||
static_shared_state() : shared_state{std::array<char, BufferSize>::data(),
|
||||
std::array<char, BufferSize>::size()}
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
struct wired_up_t {};
|
||||
constexpr static wired_up_t wired_up{};
|
||||
|
||||
struct set_transaction_function
|
||||
{
|
||||
void * begin_transaction_this = nullptr;
|
||||
void (*begin_transaction_func)(void*) = nullptr;
|
||||
|
||||
template<typename BeginTransaction>
|
||||
set_transaction_function(BeginTransaction & transaction)
|
||||
: begin_transaction_this(&transaction)
|
||||
, begin_transaction_func(
|
||||
+[](void * ptr)
|
||||
{
|
||||
(*static_cast<BeginTransaction*>(ptr))();
|
||||
})
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
struct promise_type
|
||||
{
|
||||
template<typename State, typename ... Rest>
|
||||
void * operator new(const std::size_t size, State & st, Rest &&...)
|
||||
{
|
||||
return st.resource.allocate(size);
|
||||
}
|
||||
|
||||
template<typename ... Rest>
|
||||
void operator delete(void * raw, const std::size_t size, Rest && ...) noexcept;
|
||||
void operator delete(void *, const std::size_t) noexcept {}
|
||||
|
||||
template<typename ... Rest>
|
||||
promise_type(shared_state & st, Rest & ...)
|
||||
: state(&st)
|
||||
{
|
||||
}
|
||||
|
||||
intrusive_ptr<shared_state> state;
|
||||
asio::cancellation_slot cancel;
|
||||
|
||||
using executor_type = executor;
|
||||
const executor_type & get_executor() const { return state->get_executor(); }
|
||||
|
||||
#if defined(BOOST_COBALT_NO_PMR)
|
||||
using allocator_type = detail::monotonic_allocator<void>;
|
||||
const allocator_type get_allocator() const { return &state->resource; }
|
||||
#else
|
||||
using allocator_type = pmr::polymorphic_allocator<void>;
|
||||
const allocator_type get_allocator() const { return &state->resource; }
|
||||
#endif
|
||||
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
cancellation_slot_type get_cancellation_slot() const { return cancel; }
|
||||
|
||||
constexpr static std::suspend_never initial_suspend() noexcept {return {};}
|
||||
|
||||
struct final_awaitable
|
||||
{
|
||||
promise_type * self;
|
||||
bool await_ready() noexcept
|
||||
{
|
||||
return self->state->use_count != 1u;
|
||||
}
|
||||
|
||||
std::coroutine_handle<void> await_suspend(std::coroutine_handle<promise_type> h) noexcept
|
||||
{
|
||||
auto pp = h.promise().state.detach();
|
||||
|
||||
#if defined(BOOST_COBALT_NO_SELF_DELETE)
|
||||
h.promise().~promise_type();
|
||||
#else
|
||||
// mem is in a monotonic_resource, this is fine on msvc- gcc doesn't like it though
|
||||
h.destroy();
|
||||
#endif
|
||||
pp->use_count--;
|
||||
BOOST_ASSERT(pp->use_count == 0u);
|
||||
if (pp->coro)
|
||||
return pp->coro.release();
|
||||
else
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
constexpr static void await_resume() noexcept {}
|
||||
};
|
||||
final_awaitable final_suspend() noexcept
|
||||
{
|
||||
if (cancel.is_connected())
|
||||
cancel.clear();
|
||||
return final_awaitable{this};
|
||||
}
|
||||
void return_void()
|
||||
{
|
||||
}
|
||||
|
||||
template<awaitable<promise_type> Aw>
|
||||
struct wrapped_awaitable
|
||||
{
|
||||
Aw & aw;
|
||||
constexpr static bool await_ready() noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto await_suspend(std::coroutine_handle<promise_type> h)
|
||||
{
|
||||
BOOST_ASSERT(h.promise().state->wired_up());
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
if constexpr (requires {aw.await_suspend(h, boost::source_location ());})
|
||||
return aw.await_suspend(h, h.promise().state->loc);
|
||||
#endif
|
||||
return aw.await_suspend(h);
|
||||
}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
return aw.await_resume();
|
||||
}
|
||||
};
|
||||
|
||||
template<awaitable<promise_type> Aw>
|
||||
auto await_transform(Aw & aw)
|
||||
{
|
||||
return wrapped_awaitable<Aw>{aw};
|
||||
}
|
||||
|
||||
struct wired_up_awaitable
|
||||
{
|
||||
promise_type * promise;
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return promise->state->wired_up();
|
||||
}
|
||||
void await_suspend(std::coroutine_handle<promise_type>)
|
||||
{
|
||||
}
|
||||
constexpr static void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
auto await_transform(wired_up_t)
|
||||
{
|
||||
return wired_up_awaitable{this};
|
||||
}
|
||||
|
||||
|
||||
auto await_transform(set_transaction_function sf)
|
||||
{
|
||||
begin_transaction_this = sf.begin_transaction_this;
|
||||
begin_transaction_func = sf.begin_transaction_func;
|
||||
return std::suspend_never();
|
||||
}
|
||||
|
||||
auto await_transform(asio::cancellation_slot slot)
|
||||
{
|
||||
this->cancel = slot;
|
||||
return std::suspend_never();
|
||||
}
|
||||
|
||||
[[noreturn]] void unhandled_exception() noexcept {std::terminate();}
|
||||
|
||||
void * begin_transaction_this = nullptr;
|
||||
void (*begin_transaction_func)(void*) = nullptr;
|
||||
|
||||
void begin_transaction()
|
||||
{
|
||||
if (begin_transaction_this)
|
||||
begin_transaction_func(begin_transaction_this);
|
||||
}
|
||||
|
||||
fork get_return_object()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] bool done() const
|
||||
{
|
||||
return ! handle_ || handle_.done();
|
||||
}
|
||||
|
||||
auto release() -> std::coroutine_handle<promise_type>
|
||||
{
|
||||
return handle_.release();
|
||||
}
|
||||
|
||||
private:
|
||||
fork(promise_type * pt) : handle_(pt) {}
|
||||
|
||||
unique_handle<promise_type> handle_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
#endif //BOOST_COBALT_DETAIL_FORK_HPP
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_FORWARD_CANCELLATION_HPP
|
||||
#define BOOST_COBALT_DETAIL_FORWARD_CANCELLATION_HPP
|
||||
|
||||
#include <boost/cobalt/config.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
// Requests cancellation where a successful cancellation results
|
||||
// in no apparent side effects and where the op can re-awaited.
|
||||
template<typename Awaitable>
|
||||
concept interruptible =
|
||||
( std::is_rvalue_reference_v<Awaitable> && requires (Awaitable && t) {std::move(t).interrupt_await();})
|
||||
|| (!std::is_rvalue_reference_v<Awaitable> && requires (Awaitable t) {t.interrupt_await();});
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct forward_cancellation
|
||||
{
|
||||
asio::cancellation_signal &cancel_signal;
|
||||
|
||||
forward_cancellation(asio::cancellation_signal &cancel_signal) : cancel_signal(cancel_signal) {}
|
||||
void operator()(asio::cancellation_type ct) const
|
||||
{
|
||||
cancel_signal.emit(ct);
|
||||
}
|
||||
};
|
||||
|
||||
struct forward_dispatch_cancellation
|
||||
{
|
||||
asio::cancellation_signal &cancel_signal;
|
||||
executor exec;
|
||||
|
||||
forward_dispatch_cancellation(asio::cancellation_signal &cancel_signal,
|
||||
executor exec) : cancel_signal(cancel_signal), exec(exec) {}
|
||||
void operator()(asio::cancellation_type ct) const
|
||||
{
|
||||
asio::dispatch(exec, [this, ct]{cancel_signal.emit(ct);});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_FORWARD_CANCELLATION_HPP
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_GATHER_HPP
|
||||
#define BOOST_COBALT_DETAIL_GATHER_HPP
|
||||
|
||||
#include <boost/cobalt/detail/await_result_helper.hpp>
|
||||
#include <boost/cobalt/detail/exception.hpp>
|
||||
#include <boost/cobalt/detail/fork.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/util.hpp>
|
||||
#include <boost/cobalt/detail/wrapper.hpp>
|
||||
#include <boost/cobalt/task.hpp>
|
||||
#include <boost/cobalt/this_thread.hpp>
|
||||
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/system/result.hpp>
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <coroutine>
|
||||
#include <algorithm>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
|
||||
template<typename ... Args>
|
||||
struct gather_variadic_impl
|
||||
{
|
||||
using tuple_type = std::tuple<decltype(get_awaitable_type(std::declval<Args&&>()))...>;
|
||||
|
||||
gather_variadic_impl(Args && ... args)
|
||||
: args{std::forward<Args>(args)...}
|
||||
{
|
||||
}
|
||||
|
||||
std::tuple<Args...> args;
|
||||
|
||||
constexpr static std::size_t tuple_size = sizeof...(Args);
|
||||
|
||||
struct awaitable : fork::static_shared_state<256 * tuple_size>
|
||||
{
|
||||
template<std::size_t ... Idx>
|
||||
awaitable(std::tuple<Args...> & args, std::index_sequence<Idx...>)
|
||||
: aws(awaitable_type_getter<Args>(std::get<Idx>(args))...)
|
||||
{
|
||||
}
|
||||
|
||||
tuple_type aws;
|
||||
std::array<asio::cancellation_signal, tuple_size> cancel;
|
||||
|
||||
template<typename T>
|
||||
using result_store_part = variant2::variant<
|
||||
variant2::monostate,
|
||||
void_as_monostate<co_await_result_t<T>>,
|
||||
std::exception_ptr>;
|
||||
|
||||
std::tuple<result_store_part<Args>...> result;
|
||||
|
||||
|
||||
template<std::size_t Idx>
|
||||
void interrupt_await_step()
|
||||
{
|
||||
using type= std::tuple_element_t<Idx, std::tuple<Args...>>;
|
||||
using t = std::conditional_t<
|
||||
std::is_reference_v<std::tuple_element_t<Idx, decltype(aws)>>,
|
||||
co_awaitable_type<type> &,
|
||||
co_awaitable_type<type> &&>;
|
||||
|
||||
if constexpr (interruptible<t>)
|
||||
static_cast<t>(std::get<Idx>(aws)).interrupt_await();
|
||||
}
|
||||
void interrupt_await()
|
||||
{
|
||||
mp11::mp_for_each<mp11::mp_iota_c<sizeof...(Args)>>
|
||||
([&](auto idx)
|
||||
{
|
||||
interrupt_await_step<idx>();
|
||||
});
|
||||
}
|
||||
|
||||
// GCC doesn't like member funs
|
||||
template<std::size_t Idx>
|
||||
static detail::fork await_impl(awaitable & this_)
|
||||
try
|
||||
{
|
||||
auto & aw = std::get<Idx>(this_.aws);
|
||||
// check manually if we're ready
|
||||
auto rd = aw.await_ready();
|
||||
if (!rd)
|
||||
{
|
||||
co_await this_.cancel[Idx].slot();
|
||||
// make sure the executor is set
|
||||
co_await detail::fork::wired_up;
|
||||
// do the await - this doesn't call await-ready again
|
||||
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
{
|
||||
co_await aw;
|
||||
std::get<Idx>(this_.result).template emplace<1u>();
|
||||
}
|
||||
else
|
||||
std::get<Idx>(this_.result).template emplace<1u>(co_await aw);
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
{
|
||||
aw.await_resume();
|
||||
std::get<Idx>(this_.result).template emplace<1u>();
|
||||
}
|
||||
else
|
||||
std::get<Idx>(this_.result).template emplace<1u>(aw.await_resume());
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
std::get<Idx>(this_.result).template emplace<2u>(std::current_exception());
|
||||
}
|
||||
|
||||
std::array<detail::fork(*)(awaitable&), tuple_size> impls {
|
||||
[]<std::size_t ... Idx>(std::index_sequence<Idx...>)
|
||||
{
|
||||
return std::array<detail::fork(*)(awaitable&), tuple_size>{&await_impl<Idx>...};
|
||||
}(std::make_index_sequence<tuple_size>{})
|
||||
};
|
||||
|
||||
detail::fork last_forked;
|
||||
std::size_t last_index = 0u;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
while (last_index < tuple_size)
|
||||
{
|
||||
last_forked = impls[last_index++](*this);
|
||||
if (!last_forked.done())
|
||||
return false; // one coro didn't immediately complete!
|
||||
}
|
||||
last_forked.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename H>
|
||||
auto await_suspend(
|
||||
std::coroutine_handle<H> h
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
this->loc = loc;
|
||||
#endif
|
||||
this->exec = &cobalt::detail::get_executor(h);
|
||||
last_forked.release().resume();
|
||||
while (last_index < tuple_size)
|
||||
impls[last_index++](*this).release();
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
// arm the cancel
|
||||
assign_cancellation(
|
||||
h,
|
||||
[&](asio::cancellation_type ct)
|
||||
{
|
||||
for (auto & cs : cancel)
|
||||
cs.emit(ct);
|
||||
});
|
||||
|
||||
|
||||
this->coro.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
using result_part = system::result<co_await_result_t<T>, std::exception_ptr>;
|
||||
|
||||
#if _MSC_VER
|
||||
BOOST_NOINLINE
|
||||
#endif
|
||||
std::tuple<result_part<Args> ...> await_resume()
|
||||
{
|
||||
return mp11::tuple_transform(
|
||||
[]<typename T>(variant2::variant<variant2::monostate, T, std::exception_ptr> & var)
|
||||
-> system::result<monostate_as_void<T>, std::exception_ptr>
|
||||
{
|
||||
BOOST_ASSERT(var.index() != 0u);
|
||||
if (var.index() == 1u)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, variant2::monostate>)
|
||||
return {system::in_place_value};
|
||||
else
|
||||
return {system::in_place_value, std::move(get<1>(var))};
|
||||
}
|
||||
else
|
||||
return {system::in_place_error, std::move(get<2>(var))};
|
||||
|
||||
}
|
||||
, result);
|
||||
}
|
||||
};
|
||||
awaitable operator co_await() &&
|
||||
{
|
||||
return awaitable(args, std::make_index_sequence<sizeof...(Args)>{});
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Range>
|
||||
struct gather_ranged_impl
|
||||
{
|
||||
Range aws;
|
||||
|
||||
using result_type = system::result<
|
||||
co_await_result_t<std::decay_t<decltype(*std::begin(std::declval<Range>()))>>,
|
||||
std::exception_ptr>;
|
||||
|
||||
using result_storage_type = variant2::variant<
|
||||
variant2::monostate,
|
||||
void_as_monostate<
|
||||
co_await_result_t<std::decay_t<decltype(*std::begin(std::declval<Range>()))>>
|
||||
>,
|
||||
std::exception_ptr>;
|
||||
|
||||
struct awaitable : fork::shared_state
|
||||
{
|
||||
using type = std::decay_t<decltype(*std::begin(std::declval<Range>()))>;
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::polymorphic_allocator<void> alloc{&resource};
|
||||
std::conditional_t<awaitable_type<type>, Range &,
|
||||
pmr::vector<co_awaitable_type<type>>> aws;
|
||||
|
||||
pmr::vector<bool> ready{std::size(aws), alloc};
|
||||
pmr::vector<asio::cancellation_signal> cancel{std::size(aws), alloc};
|
||||
pmr::vector<result_storage_type> result{cancel.size(), alloc};
|
||||
|
||||
#else
|
||||
std::allocator<void> alloc{};
|
||||
std::conditional_t<awaitable_type<type>, Range &,
|
||||
std::vector<co_awaitable_type<type>>> aws;
|
||||
|
||||
std::vector<bool> ready{std::size(aws), alloc};
|
||||
std::vector<asio::cancellation_signal> cancel{std::size(aws), alloc};
|
||||
std::vector<result_storage_type> result{cancel.size(), alloc};
|
||||
#endif
|
||||
|
||||
|
||||
awaitable(Range & aws_, std::false_type /* needs operator co_await */)
|
||||
: fork::shared_state((512 + sizeof(co_awaitable_type<type>)) * std::size(aws_))
|
||||
, aws{alloc}
|
||||
, ready{std::size(aws_), alloc}
|
||||
, cancel{std::size(aws_), alloc}
|
||||
{
|
||||
aws.reserve(std::size(aws_));
|
||||
for (auto && a : aws_)
|
||||
{
|
||||
using a_0 = std::decay_t<decltype(a)>;
|
||||
using a_t = std::conditional_t<
|
||||
std::is_lvalue_reference_v<Range>, a_0 &, a_0 &&>;
|
||||
aws.emplace_back(awaitable_type_getter<a_t>(static_cast<a_t>(a)));
|
||||
}
|
||||
|
||||
|
||||
std::transform(std::begin(this->aws),
|
||||
std::end(this->aws),
|
||||
std::begin(ready),
|
||||
[](auto & aw) {return aw.await_ready();});
|
||||
}
|
||||
awaitable(Range & aws, std::true_type /* needs operator co_await */)
|
||||
: fork::shared_state((512 + sizeof(co_awaitable_type<type>)) * std::size(aws))
|
||||
, aws(aws)
|
||||
{
|
||||
std::transform(std::begin(aws), std::end(aws), std::begin(ready), [](auto & aw) {return aw.await_ready();});
|
||||
}
|
||||
|
||||
awaitable(Range & aws)
|
||||
: awaitable(aws, std::bool_constant<awaitable_type<type>>{})
|
||||
{
|
||||
}
|
||||
|
||||
void interrupt_await()
|
||||
{
|
||||
using t = std::conditional_t<std::is_reference_v<Range>,
|
||||
co_awaitable_type<type> &,
|
||||
co_awaitable_type<type> &&>;
|
||||
|
||||
if constexpr (interruptible<t>)
|
||||
for (auto & aw : aws)
|
||||
static_cast<t>(aw).interrupt_await();
|
||||
}
|
||||
|
||||
static detail::fork await_impl(awaitable & this_, std::size_t idx)
|
||||
try
|
||||
{
|
||||
auto & aw = *std::next(std::begin(this_.aws), idx);
|
||||
auto rd = aw.await_ready();
|
||||
if (!rd)
|
||||
{
|
||||
co_await this_.cancel[idx].slot();
|
||||
co_await detail::fork::wired_up;
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
{
|
||||
co_await aw;
|
||||
this_.result[idx].template emplace<1u>();
|
||||
}
|
||||
else
|
||||
this_.result[idx].template emplace<1u>(co_await aw);
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
{
|
||||
aw.await_resume();
|
||||
this_.result[idx].template emplace<1u>();
|
||||
}
|
||||
else
|
||||
this_.result[idx].template emplace<1u>(aw.await_resume());
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
this_.result[idx].template emplace<2u>(std::current_exception());
|
||||
|
||||
}
|
||||
|
||||
detail::fork last_forked;
|
||||
std::size_t last_index = 0u;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
while (last_index < cancel.size())
|
||||
{
|
||||
last_forked = await_impl(*this, last_index++);
|
||||
if (!last_forked.done())
|
||||
return false; // one coro didn't immediately complete!
|
||||
}
|
||||
last_forked.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename H>
|
||||
auto await_suspend(
|
||||
std::coroutine_handle<H> h
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
this->loc = loc;
|
||||
#endif
|
||||
exec = &detail::get_executor(h);
|
||||
|
||||
last_forked.release().resume();
|
||||
while (last_index < cancel.size())
|
||||
await_impl(*this, last_index++).release();
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
// arm the cancel
|
||||
assign_cancellation(
|
||||
h,
|
||||
[&](asio::cancellation_type ct)
|
||||
{
|
||||
for (auto & cs : cancel)
|
||||
cs.emit(ct);
|
||||
});
|
||||
|
||||
this->coro.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
#if _MSC_VER
|
||||
BOOST_NOINLINE
|
||||
#endif
|
||||
auto await_resume()
|
||||
{
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::vector<result_type> res{result.size(), this_thread::get_allocator()};
|
||||
#else
|
||||
std::vector<result_type> res(result.size());
|
||||
#endif
|
||||
|
||||
std::transform(
|
||||
result.begin(), result.end(), res.begin(),
|
||||
[](result_storage_type & res) -> result_type
|
||||
{
|
||||
BOOST_ASSERT(res.index() != 0u);
|
||||
if (res.index() == 1u)
|
||||
{
|
||||
if constexpr (std::is_void_v<typename result_type::value_type>)
|
||||
return system::in_place_value;
|
||||
else
|
||||
return {system::in_place_value, std::move(get<1u>(res))};
|
||||
}
|
||||
else
|
||||
return {system::in_place_error, get<2u>(res)};
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
awaitable operator co_await() && {return awaitable{aws};}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_GATHER_HPP
|
||||
+600
@@ -0,0 +1,600 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_GENERATOR_HPP
|
||||
#define BOOST_COBALT_DETAIL_GENERATOR_HPP
|
||||
|
||||
#include <boost/cobalt/concepts.hpp>
|
||||
#include <boost/cobalt/result.hpp>
|
||||
#include <boost/cobalt/detail/exception.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/this_thread.hpp>
|
||||
#include <boost/cobalt/unique_handle.hpp>
|
||||
#include <boost/cobalt/detail/wrapper.hpp>
|
||||
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
#include <boost/core/exchange.hpp>
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_yield_awaitable;
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_receiver;
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_receiver_base
|
||||
{
|
||||
std::optional<Push> pushed_value;
|
||||
auto get_awaitable(const Push & push) requires std::is_copy_constructible_v<Push>
|
||||
{
|
||||
using impl = generator_receiver<Yield, Push>;
|
||||
return typename impl::awaitable{static_cast<impl*>(this), &push};
|
||||
}
|
||||
auto get_awaitable( Push && push)
|
||||
{
|
||||
using impl = generator_receiver<Yield, Push>;
|
||||
return typename impl::awaitable{static_cast<impl*>(this), &push};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Yield>
|
||||
struct generator_receiver_base<Yield, void>
|
||||
{
|
||||
bool pushed_value{false};
|
||||
|
||||
auto get_awaitable()
|
||||
{
|
||||
using impl = generator_receiver<Yield, void>;
|
||||
return typename impl::awaitable{static_cast<impl*>(this), static_cast<void*>(nullptr)};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_promise;
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_receiver : generator_receiver_base<Yield, Push>
|
||||
{
|
||||
std::exception_ptr exception;
|
||||
std::optional<Yield> result, result_buffer;
|
||||
Yield get_result()
|
||||
{
|
||||
if (result_buffer)
|
||||
{
|
||||
auto res = *std::exchange(result, std::nullopt);
|
||||
if (result_buffer)
|
||||
result.emplace(*std::exchange(result_buffer, std::nullopt));
|
||||
return res;
|
||||
}
|
||||
else
|
||||
return *std::exchange(result, std::nullopt);
|
||||
}
|
||||
bool done = false;
|
||||
unique_handle<void> awaited_from{nullptr};
|
||||
unique_handle<generator_promise<Yield, Push>> yield_from{nullptr};
|
||||
|
||||
bool lazy = false;
|
||||
|
||||
bool ready() { return exception || result || done; }
|
||||
|
||||
generator_receiver() = default;
|
||||
generator_receiver(generator_receiver && lhs)
|
||||
: generator_receiver_base<Yield, Push>{std::move(lhs.pushed_value)},
|
||||
exception(std::move(lhs.exception)), done(lhs.done),
|
||||
result(std::move(lhs.result)),
|
||||
result_buffer(std::move(lhs.result_buffer)),
|
||||
awaited_from(std::move(lhs.awaited_from)), yield_from{std::move(lhs.yield_from)},
|
||||
lazy(lhs.lazy), reference(lhs.reference), cancel_signal(lhs.cancel_signal)
|
||||
|
||||
{
|
||||
if (!lhs.done && !lhs.exception)
|
||||
{
|
||||
reference = this;
|
||||
lhs.exception = moved_from_exception();
|
||||
}
|
||||
lhs.done = true;
|
||||
}
|
||||
|
||||
~generator_receiver()
|
||||
{
|
||||
if (!done && reference == this)
|
||||
reference = nullptr;
|
||||
}
|
||||
|
||||
generator_receiver(generator_receiver * &reference, asio::cancellation_signal & cancel_signal)
|
||||
: reference(reference), cancel_signal(cancel_signal)
|
||||
{
|
||||
reference = this;
|
||||
}
|
||||
|
||||
generator_receiver * &reference;
|
||||
asio::cancellation_signal & cancel_signal;
|
||||
|
||||
using yield_awaitable = generator_yield_awaitable<Yield, Push>;
|
||||
|
||||
yield_awaitable get_yield_awaitable(generator_promise<Yield, Push> * pro) {return {pro}; }
|
||||
static yield_awaitable terminator() {return {nullptr}; }
|
||||
|
||||
template<typename T>
|
||||
void yield_value(T && t)
|
||||
{
|
||||
if (!result)
|
||||
result.emplace(std::forward<T>(t));
|
||||
else
|
||||
{
|
||||
BOOST_ASSERT(!result_buffer);
|
||||
result_buffer.emplace(std::forward<T>(t));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct awaitable
|
||||
{
|
||||
generator_receiver *self;
|
||||
std::exception_ptr ex;
|
||||
asio::cancellation_slot cl;
|
||||
|
||||
variant2::variant<variant2::monostate, Push *, const Push *> to_push;
|
||||
|
||||
|
||||
awaitable(generator_receiver * self, Push * to_push) : self(self), to_push(to_push)
|
||||
{
|
||||
}
|
||||
awaitable(generator_receiver * self, const Push * to_push)
|
||||
: self(self), to_push(to_push)
|
||||
{
|
||||
}
|
||||
|
||||
awaitable(const awaitable & aw) noexcept : self(aw.self), to_push(aw.to_push)
|
||||
{
|
||||
}
|
||||
|
||||
bool await_ready() const
|
||||
{
|
||||
BOOST_ASSERT(!ex);
|
||||
return self->ready();
|
||||
}
|
||||
|
||||
template<typename Promise>
|
||||
std::coroutine_handle<void> await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
if (self->done) // ok, so we're actually done already, so noop
|
||||
return std::noop_coroutine();
|
||||
|
||||
|
||||
if (!ex && self->awaited_from != nullptr) // generator already being awaited, that's an error!
|
||||
ex = already_awaited();
|
||||
|
||||
if (ex)
|
||||
return h;
|
||||
|
||||
if constexpr (requires (Promise p) {p.get_cancellation_slot();})
|
||||
if ((cl = h.promise().get_cancellation_slot()).is_connected())
|
||||
cl.emplace<forward_cancellation>(self->cancel_signal);
|
||||
|
||||
self->awaited_from.reset(h.address());
|
||||
|
||||
std::coroutine_handle<void> res = std::noop_coroutine();
|
||||
if (self->yield_from != nullptr)
|
||||
res = self->yield_from.release();
|
||||
|
||||
if ((to_push.index() > 0) && !self->pushed_value && self->lazy)
|
||||
{
|
||||
if constexpr (std::is_void_v<Push>)
|
||||
self->pushed_value = true;
|
||||
else
|
||||
{
|
||||
if (to_push.index() == 1)
|
||||
self->pushed_value.emplace(std::move(*variant2::get<1>(to_push)));
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_copy_constructible_v<Push>)
|
||||
self->pushed_value.emplace(std::move(*variant2::get<2>(to_push)));
|
||||
else
|
||||
{
|
||||
BOOST_ASSERT(!"push value is not movable");
|
||||
}
|
||||
}
|
||||
}
|
||||
to_push = variant2::monostate{};
|
||||
}
|
||||
return std::coroutine_handle<void>::from_address(res.address());
|
||||
}
|
||||
|
||||
Yield await_resume(const boost::source_location & loc = BOOST_CURRENT_LOCATION)
|
||||
{
|
||||
return await_resume(as_result_tag{}).value(loc);
|
||||
}
|
||||
|
||||
std::tuple<std::exception_ptr, Yield> await_resume(
|
||||
const as_tuple_tag &)
|
||||
{
|
||||
auto res = await_resume(as_result_tag{});
|
||||
if (res.has_error())
|
||||
return {res.error(), Yield{}};
|
||||
else
|
||||
return {nullptr, res.value()};
|
||||
}
|
||||
|
||||
system::result<Yield, std::exception_ptr> await_resume(const as_result_tag& )
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
if (ex)
|
||||
return {system::in_place_error, ex};
|
||||
if (self->exception)
|
||||
return {system::in_place_error, std::exchange(self->exception, nullptr)};
|
||||
if (!self->result) // missing co_return this is accepted behaviour, if the compiler agrees
|
||||
return {system::in_place_error, std::make_exception_ptr(std::runtime_error("cobalt::generator returned void"))};
|
||||
|
||||
if (to_push.index() > 0)
|
||||
{
|
||||
BOOST_ASSERT(!self->pushed_value);
|
||||
if constexpr (std::is_void_v<Push>)
|
||||
self->pushed_value = true;
|
||||
else
|
||||
{
|
||||
if (to_push.index() == 1)
|
||||
self->pushed_value.emplace(std::move(*variant2::get<1>(to_push)));
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_copy_constructible_v<Push>)
|
||||
self->pushed_value.emplace(std::move(*variant2::get<2>(to_push)));
|
||||
else
|
||||
{
|
||||
BOOST_ASSERT(!"push value is not movable");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
to_push = variant2::monostate{};
|
||||
}
|
||||
|
||||
// now we also want to resume the coroutine, so it starts work
|
||||
if (self->yield_from != nullptr && !self->lazy)
|
||||
{
|
||||
auto exec = self->yield_from->get_executor();
|
||||
auto alloc = asio::get_associated_allocator(self->yield_from);
|
||||
asio::post(
|
||||
std::move(exec),
|
||||
asio::bind_allocator(
|
||||
alloc,
|
||||
[y = std::exchange(self->yield_from, nullptr)]() mutable
|
||||
{
|
||||
if (y->receiver) // make sure we only resume eagerly when attached to a generator object
|
||||
std::move(y)();
|
||||
}));
|
||||
}
|
||||
|
||||
return {system::in_place_value, self->get_result()};
|
||||
}
|
||||
|
||||
void interrupt_await() &
|
||||
{
|
||||
if (!self)
|
||||
return ;
|
||||
ex = detached_exception();
|
||||
|
||||
if (self->awaited_from)
|
||||
self->awaited_from.release().resume();
|
||||
}
|
||||
};
|
||||
|
||||
void interrupt_await() &
|
||||
{
|
||||
exception = detached_exception();
|
||||
awaited_from.release().resume();
|
||||
}
|
||||
|
||||
void rethrow_if()
|
||||
{
|
||||
if (exception)
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_promise
|
||||
: promise_memory_resource_base,
|
||||
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
||||
promise_throw_if_cancelled_base,
|
||||
enable_awaitables<generator_promise<Yield, Push>>,
|
||||
enable_await_allocator<generator_promise<Yield, Push>>,
|
||||
enable_await_executor< generator_promise<Yield, Push>>
|
||||
{
|
||||
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
||||
using promise_throw_if_cancelled_base::await_transform;
|
||||
using enable_awaitables<generator_promise<Yield, Push>>::await_transform;
|
||||
using enable_await_allocator<generator_promise<Yield, Push>>::await_transform;
|
||||
using enable_await_executor<generator_promise<Yield, Push>>::await_transform;
|
||||
|
||||
[[nodiscard]] generator<Yield, Push> get_return_object()
|
||||
{
|
||||
return generator<Yield, Push>{this};
|
||||
}
|
||||
|
||||
mutable asio::cancellation_signal signal;
|
||||
|
||||
using executor_type = executor;
|
||||
executor_type exec;
|
||||
const executor_type & get_executor() const {return exec;}
|
||||
|
||||
template<typename ... Args>
|
||||
generator_promise(Args & ...args)
|
||||
:
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
promise_memory_resource_base(detail::get_memory_resource_from_args(args...)),
|
||||
#endif
|
||||
exec{detail::get_executor_from_args(args...)}
|
||||
{
|
||||
this->reset_cancellation_source(signal.slot());
|
||||
}
|
||||
|
||||
std::suspend_never initial_suspend() {return {};}
|
||||
|
||||
struct final_awaitable
|
||||
{
|
||||
generator_promise * generator;
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return generator->receiver && generator->receiver->awaited_from.get() == nullptr;
|
||||
}
|
||||
|
||||
auto await_suspend(std::coroutine_handle<generator_promise> h) noexcept
|
||||
{
|
||||
std::coroutine_handle<void> res = std::noop_coroutine();
|
||||
if (generator->receiver && generator->receiver->awaited_from.get() != nullptr)
|
||||
res = generator->receiver->awaited_from.release();
|
||||
if (generator->receiver)
|
||||
generator->receiver->done = true;
|
||||
|
||||
|
||||
if (auto & rec = h.promise().receiver; rec != nullptr)
|
||||
{
|
||||
if (!rec->done && !rec->exception)
|
||||
rec->exception = detail::completed_unexpected();
|
||||
rec->done = true;
|
||||
rec->awaited_from.reset(nullptr);
|
||||
rec = nullptr;
|
||||
}
|
||||
|
||||
detail::self_destroy(h);
|
||||
return res;
|
||||
}
|
||||
|
||||
void await_resume() noexcept
|
||||
{
|
||||
if (generator->receiver)
|
||||
generator->receiver->done = true;
|
||||
}
|
||||
};
|
||||
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
return final_awaitable{this};
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
if (this->receiver)
|
||||
this->receiver->exception = std::current_exception();
|
||||
else
|
||||
throw ;
|
||||
}
|
||||
|
||||
void return_value(const Yield & res) requires std::is_copy_constructible_v<Yield>
|
||||
{
|
||||
if (this->receiver)
|
||||
this->receiver->yield_value(res);
|
||||
}
|
||||
|
||||
void return_value(Yield && res)
|
||||
{
|
||||
if (this->receiver)
|
||||
this->receiver->yield_value(std::move(res));
|
||||
}
|
||||
|
||||
generator_receiver<Yield, Push>* receiver{nullptr};
|
||||
|
||||
auto await_transform(this_coro::initial_t val)
|
||||
{
|
||||
if(receiver)
|
||||
{
|
||||
receiver->lazy = true;
|
||||
return receiver->get_yield_awaitable(this);
|
||||
}
|
||||
else
|
||||
return generator_receiver<Yield, Push>::terminator();
|
||||
}
|
||||
|
||||
template<typename Yield_>
|
||||
auto yield_value(Yield_ && ret)
|
||||
{
|
||||
if(receiver)
|
||||
{
|
||||
// if this is lazy, there might still be a value in there.
|
||||
receiver->yield_value(std::forward<Yield_>(ret));
|
||||
return receiver->get_yield_awaitable(this);
|
||||
}
|
||||
else
|
||||
return generator_receiver<Yield, Push>::terminator();
|
||||
}
|
||||
|
||||
void interrupt_await() &
|
||||
{
|
||||
if (this->receiver)
|
||||
{
|
||||
this->receiver->exception = detached_exception();
|
||||
std::coroutine_handle<void>::from_address(this->receiver->awaited_from.release()).resume();
|
||||
}
|
||||
}
|
||||
|
||||
~generator_promise()
|
||||
{
|
||||
if (this->receiver)
|
||||
{
|
||||
if (!this->receiver->done && !this->receiver->exception)
|
||||
this->receiver->exception = detail::completed_unexpected();
|
||||
this->receiver->done = true;
|
||||
this->receiver->awaited_from.reset(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_yield_awaitable
|
||||
{
|
||||
generator_promise<Yield, Push> *self;
|
||||
constexpr bool await_ready() const
|
||||
{
|
||||
return self && self->receiver && self->receiver->pushed_value && !self->receiver->result;
|
||||
}
|
||||
|
||||
std::coroutine_handle<void> await_suspend(
|
||||
std::coroutine_handle<generator_promise<Yield, Push>> h
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (self == nullptr) // we're a terminator, kill it
|
||||
{
|
||||
if (auto & rec = h.promise().receiver; rec != nullptr)
|
||||
{
|
||||
if (!rec->done && !rec->exception)
|
||||
rec->exception = detail::completed_unexpected();
|
||||
rec->done = true;
|
||||
rec->awaited_from.reset(nullptr);
|
||||
rec = nullptr;
|
||||
}
|
||||
|
||||
detail::self_destroy(h);
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
std::coroutine_handle<void> res = std::noop_coroutine();
|
||||
if (self->receiver->awaited_from.get() != nullptr)
|
||||
res = self->receiver->awaited_from.release();
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
self->receiver->yield_from.reset(&h.promise(), loc);
|
||||
#else
|
||||
self->receiver->yield_from.reset(&h.promise());
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
Push await_resume()
|
||||
{
|
||||
BOOST_ASSERT(self->receiver);
|
||||
BOOST_ASSERT(self->receiver->pushed_value);
|
||||
return *std::exchange(self->receiver->pushed_value, std::nullopt);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Yield>
|
||||
struct generator_yield_awaitable<Yield, void>
|
||||
{
|
||||
generator_promise<Yield, void> *self;
|
||||
constexpr bool await_ready() { return self && self->receiver && self->receiver->pushed_value; }
|
||||
|
||||
std::coroutine_handle<> await_suspend(
|
||||
std::coroutine_handle<generator_promise<Yield, void>> h
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (self == nullptr) // we're a terminator, kill it
|
||||
{
|
||||
if (auto & rec = h.promise().receiver; rec != nullptr)
|
||||
{
|
||||
if (!rec->done && !rec->exception)
|
||||
rec->exception = detail::completed_unexpected();
|
||||
rec->done = true;
|
||||
rec->awaited_from.reset(nullptr);
|
||||
rec = nullptr;
|
||||
}
|
||||
detail::self_destroy(h);
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
std::coroutine_handle<void> res = std::noop_coroutine();
|
||||
BOOST_ASSERT(self);
|
||||
if (self->receiver->awaited_from.get() != nullptr)
|
||||
res = self->receiver->awaited_from.release();
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
self->receiver->yield_from.reset(&h.promise(), loc);
|
||||
#else
|
||||
self->receiver->yield_from.reset(&h.promise());
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
void await_resume()
|
||||
{
|
||||
BOOST_ASSERT(self->receiver->pushed_value);
|
||||
self->receiver->pushed_value = false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
template<typename Yield, typename Push>
|
||||
struct generator_base
|
||||
{
|
||||
auto operator()( Push && push)
|
||||
{
|
||||
return static_cast<generator<Yield, Push>*>(this)->receiver_.get_awaitable(std::move(push));
|
||||
}
|
||||
auto operator()(const Push & push) requires std::is_copy_constructible_v<Push>
|
||||
{
|
||||
return static_cast<generator<Yield, Push>*>(this)->receiver_.get_awaitable(push);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Yield>
|
||||
struct generator_base<Yield, void>
|
||||
{
|
||||
auto operator co_await ()
|
||||
{
|
||||
return static_cast<generator<Yield, void>*>(this)->receiver_.get_awaitable();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct generator_with_awaitable
|
||||
{
|
||||
generator_base<T, void> &g;
|
||||
std::optional<typename detail::generator_receiver<T, void>::awaitable> awaitable;
|
||||
|
||||
template<typename Promise>
|
||||
void await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
g.cancel();
|
||||
awaitable.emplace(g.operator co_await());
|
||||
return awaitable->await_suspend(h);
|
||||
}
|
||||
|
||||
void await_resume() {}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_GENERATOR_HPP
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// 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 BOOST_COBALT_HANDLER_HPP
|
||||
#define BOOST_COBALT_HANDLER_HPP
|
||||
|
||||
#include <boost/cobalt/this_coro.hpp>
|
||||
#include <boost/cobalt/unique_handle.hpp>
|
||||
#include <boost/cobalt/detail/util.hpp>
|
||||
|
||||
#include <boost/cobalt/detail/sbo_resource.hpp>
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/system/result.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
enum class completed_immediately_t
|
||||
{
|
||||
no, maybe, yes, initiating
|
||||
};
|
||||
|
||||
struct completion_handler_noop_executor
|
||||
{
|
||||
executor exec;
|
||||
completed_immediately_t * completed_immediately = nullptr;
|
||||
|
||||
template<typename Fn>
|
||||
void execute(Fn && fn) const
|
||||
{
|
||||
// only allow it when we're still initializing
|
||||
if (completed_immediately &&
|
||||
((*completed_immediately == completed_immediately_t::initiating)
|
||||
|| (*completed_immediately == completed_immediately_t::maybe)))
|
||||
{
|
||||
// only use this indicator if the fn will actually call our completion-handler
|
||||
// otherwise this was a single op in a composed operation
|
||||
*completed_immediately = completed_immediately_t::maybe;
|
||||
fn();
|
||||
// yes means completion_handler::operator() was called, so we're good.
|
||||
if (*completed_immediately != completed_immediately_t::yes)
|
||||
*completed_immediately = completed_immediately_t::initiating;
|
||||
}
|
||||
else
|
||||
{
|
||||
asio::post(exec, std::forward<Fn>(fn));
|
||||
}
|
||||
}
|
||||
|
||||
friend bool operator==(const completion_handler_noop_executor&, const completion_handler_noop_executor&) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
friend bool operator!=(const completion_handler_noop_executor&, const completion_handler_noop_executor&) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
completion_handler_noop_executor(const completion_handler_noop_executor & rhs) noexcept = default;
|
||||
completion_handler_noop_executor(cobalt::executor inner, completed_immediately_t * completed_immediately)
|
||||
: exec(std::move(inner)), completed_immediately(completed_immediately)
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct completion_handler_base
|
||||
{
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
cancellation_slot_type cancellation_slot ;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return cancellation_slot ;
|
||||
}
|
||||
|
||||
using executor_type = executor;
|
||||
const executor_type & executor_ ;
|
||||
const executor_type & get_executor() const noexcept
|
||||
{
|
||||
return executor_ ;
|
||||
}
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
using allocator_type = pmr::polymorphic_allocator<void>;
|
||||
pmr::polymorphic_allocator<void> allocator ;
|
||||
allocator_type get_allocator() const noexcept
|
||||
{
|
||||
return allocator ;
|
||||
}
|
||||
#else
|
||||
using allocator_type = detail::sbo_allocator<void>;
|
||||
detail::sbo_allocator<void> allocator ;
|
||||
allocator_type get_allocator() const noexcept
|
||||
{
|
||||
return allocator ;
|
||||
}
|
||||
#endif
|
||||
using immediate_executor_type = completion_handler_noop_executor;
|
||||
completed_immediately_t * completed_immediately = nullptr;
|
||||
immediate_executor_type get_immediate_executor() const noexcept
|
||||
{
|
||||
return {get_executor(), completed_immediately};
|
||||
}
|
||||
|
||||
template<typename Promise>
|
||||
requires (requires (Promise p) {{p.get_executor()} -> std::same_as<const executor&>;})
|
||||
completion_handler_base(std::coroutine_handle<Promise> h,
|
||||
completed_immediately_t * completed_immediately = nullptr)
|
||||
: cancellation_slot(asio::get_associated_cancellation_slot(h.promise())),
|
||||
executor_(h.promise().get_executor()),
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
allocator(asio::get_associated_allocator(h.promise(), this_thread::get_allocator())),
|
||||
#else
|
||||
allocator(detail::get_null_sbo_resource()),
|
||||
#endif
|
||||
completed_immediately(completed_immediately)
|
||||
{
|
||||
}
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
template<typename Promise>
|
||||
requires (requires (Promise p) {{p.get_executor()} -> std::same_as<const executor&>;})
|
||||
completion_handler_base(std::coroutine_handle<Promise> h,
|
||||
pmr::memory_resource * resource,
|
||||
completed_immediately_t * completed_immediately = nullptr)
|
||||
: cancellation_slot(asio::get_associated_cancellation_slot(h.promise())),
|
||||
executor_(h.promise().get_executor()),
|
||||
allocator(resource),
|
||||
completed_immediately(completed_immediately)
|
||||
{
|
||||
}
|
||||
#else
|
||||
template<typename Promise>
|
||||
requires (requires (Promise p) {{p.get_executor()} -> std::same_as<const executor&>;})
|
||||
completion_handler_base(std::coroutine_handle<Promise> h,
|
||||
detail::sbo_resource * resource,
|
||||
completed_immediately_t * completed_immediately = nullptr)
|
||||
: cancellation_slot(asio::get_associated_cancellation_slot(h.promise())),
|
||||
executor_(h.promise().get_executor()),
|
||||
allocator(resource),
|
||||
completed_immediately(completed_immediately)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
template<typename Handler>
|
||||
void assign_cancellation(std::coroutine_handle<void>, Handler &&) {}
|
||||
|
||||
template<typename Promise, typename Handler>
|
||||
void assign_cancellation(std::coroutine_handle<Promise> h, Handler && func)
|
||||
{
|
||||
if constexpr (requires {h.promise().get_cancellation_slot();})
|
||||
if (h.promise().get_cancellation_slot().is_connected())
|
||||
h.promise().get_cancellation_slot().assign(std::forward<Handler>(func));
|
||||
}
|
||||
|
||||
template<typename Promise>
|
||||
const executor &
|
||||
get_executor(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
if constexpr (requires {h.promise().get_executor();})
|
||||
{
|
||||
static_assert(std::same_as<decltype(h.promise().get_executor()),
|
||||
const executor &>,
|
||||
"for performance reasons, the get_executor function on a promise must return a const reference");
|
||||
return h.promise().get_executor();
|
||||
}
|
||||
else
|
||||
return this_thread::get_executor();
|
||||
}
|
||||
|
||||
inline const executor &
|
||||
get_executor(std::coroutine_handle<>)
|
||||
{
|
||||
return this_thread::get_executor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename ... Args>
|
||||
struct handler
|
||||
{
|
||||
void operator()(Args ... args)
|
||||
{
|
||||
result.emplace(static_cast<Args>(args)...);
|
||||
}
|
||||
handler(std::optional<std::tuple<Args...>> &result) : result(result) {}
|
||||
private:
|
||||
std::optional<std::tuple<Args...>> &result;
|
||||
};
|
||||
|
||||
template<typename ... Args>
|
||||
handler(std::optional<std::tuple<Args...>> &result) -> handler<Args...>;
|
||||
|
||||
template<typename ... Args>
|
||||
struct completion_handler : detail::completion_handler_base
|
||||
{
|
||||
completion_handler(completion_handler && ) = default;
|
||||
|
||||
template<typename Promise>
|
||||
completion_handler(std::coroutine_handle<Promise> h,
|
||||
std::optional<std::tuple<Args...>> &result,
|
||||
detail::completed_immediately_t * completed_immediately = nullptr
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
) : completion_handler_base(h, completed_immediately),
|
||||
self(h.address()), result(result)
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, loc_(loc)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
template<typename Promise>
|
||||
completion_handler(std::coroutine_handle<Promise> h,
|
||||
std::optional<std::tuple<Args...>> &result,
|
||||
pmr::memory_resource * resource,
|
||||
detail::completed_immediately_t * completed_immediately = nullptr
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
) : completion_handler_base(h, resource, completed_immediately),
|
||||
self(h.address()), result(result)
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, loc_(loc)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
#else
|
||||
template<typename Promise>
|
||||
completion_handler(std::coroutine_handle<Promise> h,
|
||||
std::optional<std::tuple<Args...>> &result,
|
||||
detail::sbo_resource * resource,
|
||||
detail::completed_immediately_t * completed_immediately = nullptr)
|
||||
: completion_handler_base(h, resource, completed_immediately),
|
||||
self(h.address()), result(result)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void operator()(Args ... args)
|
||||
{
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
BOOST_ASIO_HANDLER_LOCATION((loc_.file_name(), loc_.line(), loc_.function_name()));
|
||||
#endif
|
||||
result.emplace(std::move(args)...);
|
||||
BOOST_ASSERT(this->self != nullptr);
|
||||
auto p = this->self.release();
|
||||
if (completed_immediately != nullptr
|
||||
&& *completed_immediately == detail::completed_immediately_t::maybe)
|
||||
{
|
||||
*completed_immediately = detail::completed_immediately_t::yes;
|
||||
return;
|
||||
}
|
||||
|
||||
std::move(p)();
|
||||
}
|
||||
using result_type = std::optional<std::tuple<Args...>>;
|
||||
|
||||
~completion_handler()
|
||||
{
|
||||
if (self && completed_immediately
|
||||
&& *completed_immediately == detail::completed_immediately_t::initiating
|
||||
&& std::uncaught_exceptions() > 0)
|
||||
self.release();
|
||||
}
|
||||
private:
|
||||
unique_handle<void> self;
|
||||
std::optional<std::tuple<Args...>> &result;
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
boost::source_location loc_;
|
||||
#endif
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif //BOOST_COBALT_HANDLER_HPP
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_JOIN_HPP
|
||||
#define BOOST_COBALT_DETAIL_JOIN_HPP
|
||||
|
||||
#include <boost/cobalt/detail/await_result_helper.hpp>
|
||||
#include <boost/cobalt/detail/exception.hpp>
|
||||
#include <boost/cobalt/detail/fork.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/util.hpp>
|
||||
#include <boost/cobalt/detail/wrapper.hpp>
|
||||
#include <boost/cobalt/task.hpp>
|
||||
#include <boost/cobalt/this_thread.hpp>
|
||||
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/system/result.hpp>
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <coroutine>
|
||||
#include <algorithm>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
template<typename ... Args>
|
||||
struct join_variadic_impl
|
||||
{
|
||||
using tuple_type = std::tuple<decltype(get_awaitable_type(std::declval<Args&&>()))...>;
|
||||
|
||||
join_variadic_impl(Args && ... args)
|
||||
: args{std::forward<Args>(args)...}
|
||||
{
|
||||
}
|
||||
|
||||
std::tuple<Args...> args;
|
||||
|
||||
constexpr static std::size_t tuple_size = sizeof...(Args);
|
||||
|
||||
struct awaitable : fork::static_shared_state<256 * tuple_size>
|
||||
{
|
||||
template<std::size_t ... Idx>
|
||||
awaitable(std::tuple<Args...> & args, std::index_sequence<Idx...>) :
|
||||
aws(awaitable_type_getter<Args>(std::get<Idx>(args))...)
|
||||
{
|
||||
}
|
||||
|
||||
tuple_type aws;
|
||||
|
||||
std::array<asio::cancellation_signal, tuple_size> cancel_;
|
||||
template<typename > constexpr static auto make_null() {return nullptr;};
|
||||
std::array<asio::cancellation_signal*, tuple_size> cancel = {make_null<Args>()...};
|
||||
|
||||
constexpr static bool all_void = (std::is_void_v<co_await_result_t<Args>> && ...);
|
||||
template<typename T>
|
||||
using result_store_part =
|
||||
std::optional<void_as_monostate<co_await_result_t<T>>>;
|
||||
|
||||
std::conditional_t<all_void,
|
||||
variant2::monostate,
|
||||
std::tuple<result_store_part<Args>...>> result;
|
||||
std::exception_ptr error;
|
||||
|
||||
template<std::size_t Idx>
|
||||
void cancel_step()
|
||||
{
|
||||
auto &r = cancel[Idx];
|
||||
if (r)
|
||||
std::exchange(r, nullptr)->emit(asio::cancellation_type::all);
|
||||
}
|
||||
|
||||
void cancel_all()
|
||||
{
|
||||
mp11::mp_for_each<mp11::mp_iota_c<sizeof...(Args)>>
|
||||
([&](auto idx)
|
||||
{
|
||||
cancel_step<idx>();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<std::size_t Idx>
|
||||
void interrupt_await_step()
|
||||
{
|
||||
using type = std::tuple_element_t<Idx, tuple_type>;
|
||||
using t = std::conditional_t<std::is_reference_v<std::tuple_element_t<Idx, std::tuple<Args...>>>,
|
||||
type &,
|
||||
type &&>;
|
||||
|
||||
if constexpr (interruptible<t>)
|
||||
if (this->cancel[Idx] != nullptr)
|
||||
static_cast<t>(std::get<Idx>(aws)).interrupt_await();
|
||||
}
|
||||
|
||||
void interrupt_await()
|
||||
{
|
||||
mp11::mp_for_each<mp11::mp_iota_c<sizeof...(Args)>>
|
||||
([&](auto idx)
|
||||
{
|
||||
interrupt_await_step<idx>();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// GCC doesn't like member funs
|
||||
template<std::size_t Idx>
|
||||
static detail::fork await_impl(awaitable & this_)
|
||||
try
|
||||
{
|
||||
auto & aw = std::get<Idx>(this_.aws);
|
||||
// check manually if we're ready
|
||||
auto rd = aw.await_ready();
|
||||
if (!rd)
|
||||
{
|
||||
this_.cancel[Idx] = &this_.cancel_[Idx];
|
||||
co_await this_.cancel[Idx]->slot();
|
||||
// make sure the executor is set
|
||||
co_await detail::fork::wired_up;
|
||||
// do the await - this doesn't call await-ready again
|
||||
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
{
|
||||
co_await aw;
|
||||
if constexpr (!all_void)
|
||||
std::get<Idx>(this_.result).emplace();
|
||||
}
|
||||
else
|
||||
std::get<Idx>(this_.result).emplace(co_await aw);
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
{
|
||||
aw.await_resume();
|
||||
if constexpr (!all_void)
|
||||
std::get<Idx>(this_.result).emplace();
|
||||
}
|
||||
else
|
||||
std::get<Idx>(this_.result).emplace(aw.await_resume());
|
||||
}
|
||||
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
if (!this_.error)
|
||||
this_.error = std::current_exception();
|
||||
this_.cancel_all();
|
||||
}
|
||||
|
||||
std::array<detail::fork(*)(awaitable&), tuple_size> impls {
|
||||
[]<std::size_t ... Idx>(std::index_sequence<Idx...>)
|
||||
{
|
||||
return std::array<detail::fork(*)(awaitable&), tuple_size>{&await_impl<Idx>...};
|
||||
}(std::make_index_sequence<tuple_size>{})
|
||||
};
|
||||
|
||||
detail::fork last_forked;
|
||||
std::size_t last_index = 0u;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
while (last_index < tuple_size)
|
||||
{
|
||||
last_forked = impls[last_index++](*this);
|
||||
if (!last_forked.done())
|
||||
return false; // one coro didn't immediately complete!
|
||||
}
|
||||
last_forked.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename H>
|
||||
auto await_suspend(
|
||||
std::coroutine_handle<H> h
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
this->loc = loc;
|
||||
#endif
|
||||
this->exec = &detail::get_executor(h);
|
||||
last_forked.release().resume();
|
||||
while (last_index < tuple_size)
|
||||
impls[last_index++](*this).release();
|
||||
|
||||
if (error)
|
||||
cancel_all();
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
// arm the cancel
|
||||
assign_cancellation(
|
||||
h,
|
||||
[&](asio::cancellation_type ct)
|
||||
{
|
||||
for (auto cs : cancel)
|
||||
if (cs)
|
||||
cs->emit(ct);
|
||||
});
|
||||
|
||||
this->coro.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
#if _MSC_VER
|
||||
BOOST_NOINLINE
|
||||
#endif
|
||||
auto await_resume()
|
||||
{
|
||||
if (error)
|
||||
std::rethrow_exception(error);
|
||||
if constexpr(!all_void)
|
||||
return mp11::tuple_transform(
|
||||
[]<typename T>(std::optional<T> & var)
|
||||
-> T
|
||||
{
|
||||
BOOST_ASSERT(var.has_value());
|
||||
return std::move(*var);
|
||||
}, result);
|
||||
}
|
||||
|
||||
auto await_resume(const as_tuple_tag &)
|
||||
{
|
||||
using t = decltype(await_resume());
|
||||
if constexpr(!all_void)
|
||||
{
|
||||
if (error)
|
||||
return std::make_tuple(error, t{});
|
||||
else
|
||||
return std::make_tuple(std::current_exception(),
|
||||
mp11::tuple_transform(
|
||||
[]<typename T>(std::optional<T> & var)
|
||||
-> T
|
||||
{
|
||||
BOOST_ASSERT(var.has_value());
|
||||
return std::move(*var);
|
||||
}, result));
|
||||
}
|
||||
else
|
||||
return std::make_tuple(error);
|
||||
}
|
||||
|
||||
auto await_resume(const as_result_tag &)
|
||||
{
|
||||
using t = decltype(await_resume());
|
||||
using rt = system::result<t, std::exception_ptr>;
|
||||
if (error)
|
||||
return rt(system::in_place_error, error);
|
||||
if constexpr(!all_void)
|
||||
return mp11::tuple_transform(
|
||||
[]<typename T>(std::optional<T> & var)
|
||||
-> T
|
||||
{
|
||||
BOOST_ASSERT(var.has_value());
|
||||
return std::move(*var);
|
||||
}, result);
|
||||
else
|
||||
return system::in_place_value;
|
||||
}
|
||||
};
|
||||
awaitable operator co_await() &&
|
||||
{
|
||||
return awaitable(args, std::make_index_sequence<sizeof...(Args)>{});
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Range>
|
||||
struct join_ranged_impl
|
||||
{
|
||||
Range aws;
|
||||
|
||||
using result_type = co_await_result_t<std::decay_t<decltype(*std::begin(std::declval<Range>()))>>;
|
||||
|
||||
constexpr static std::size_t result_size =
|
||||
sizeof(std::conditional_t<std::is_void_v<result_type>, variant2::monostate, result_type>);
|
||||
|
||||
struct awaitable : fork::shared_state
|
||||
{
|
||||
struct dummy
|
||||
{
|
||||
template<typename ... Args>
|
||||
dummy(Args && ...) {}
|
||||
};
|
||||
|
||||
using type = std::decay_t<decltype(*std::begin(std::declval<Range>()))>;
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::polymorphic_allocator<void> alloc{&resource};
|
||||
|
||||
std::conditional_t<awaitable_type<type>, Range &,
|
||||
pmr::vector<co_awaitable_type<type>>> aws;
|
||||
|
||||
pmr::vector<bool> ready{std::size(aws), alloc};
|
||||
pmr::vector<asio::cancellation_signal> cancel_{std::size(aws), alloc};
|
||||
pmr::vector<asio::cancellation_signal*> cancel{std::size(aws), alloc};
|
||||
|
||||
|
||||
|
||||
std::conditional_t<
|
||||
std::is_void_v<result_type>,
|
||||
dummy,
|
||||
pmr::vector<std::optional<void_as_monostate<result_type>>>>
|
||||
result{
|
||||
cancel.size(),
|
||||
alloc};
|
||||
#else
|
||||
std::allocator<void> alloc;
|
||||
std::conditional_t<awaitable_type<type>, Range &, std::vector<co_awaitable_type<type>>> aws;
|
||||
|
||||
std::vector<bool> ready{std::size(aws), alloc};
|
||||
std::vector<asio::cancellation_signal> cancel_{std::size(aws), alloc};
|
||||
std::vector<asio::cancellation_signal*> cancel{std::size(aws), alloc};
|
||||
|
||||
std::conditional_t<
|
||||
std::is_void_v<result_type>,
|
||||
dummy,
|
||||
std::vector<std::optional<void_as_monostate<result_type>>>>
|
||||
result{
|
||||
cancel.size(),
|
||||
alloc};
|
||||
#endif
|
||||
std::exception_ptr error;
|
||||
|
||||
awaitable(Range & aws_, std::false_type /* needs operator co_await */)
|
||||
: fork::shared_state((512 + sizeof(co_awaitable_type<type>) + result_size) * std::size(aws_))
|
||||
, aws{alloc}
|
||||
, ready{std::size(aws_), alloc}
|
||||
, cancel_{std::size(aws_), alloc}
|
||||
, cancel{std::size(aws_), alloc}
|
||||
{
|
||||
aws.reserve(std::size(aws_));
|
||||
for (auto && a : aws_)
|
||||
{
|
||||
using a_0 = std::decay_t<decltype(a)>;
|
||||
using a_t = std::conditional_t<
|
||||
std::is_lvalue_reference_v<Range>, a_0 &, a_0 &&>;
|
||||
aws.emplace_back(awaitable_type_getter<a_t>(static_cast<a_t>(a)));
|
||||
}
|
||||
|
||||
std::transform(std::begin(this->aws),
|
||||
std::end(this->aws),
|
||||
std::begin(ready),
|
||||
[](auto & aw) {return aw.await_ready();});
|
||||
}
|
||||
awaitable(Range & aws, std::true_type /* needs operator co_await */)
|
||||
: fork::shared_state((512 + sizeof(co_awaitable_type<type>) + result_size) * std::size(aws))
|
||||
, aws(aws)
|
||||
{
|
||||
std::transform(std::begin(aws), std::end(aws), std::begin(ready), [](auto & aw) {return aw.await_ready();});
|
||||
}
|
||||
|
||||
awaitable(Range & aws)
|
||||
: awaitable(aws, std::bool_constant<awaitable_type<type>>{})
|
||||
{
|
||||
}
|
||||
|
||||
void cancel_all()
|
||||
{
|
||||
for (auto & r : cancel)
|
||||
if (r)
|
||||
std::exchange(r, nullptr)->emit(asio::cancellation_type::all);
|
||||
}
|
||||
|
||||
void interrupt_await()
|
||||
{
|
||||
using t = std::conditional_t<std::is_reference_v<Range>,
|
||||
co_awaitable_type<type> &,
|
||||
co_awaitable_type<type> &&>;
|
||||
|
||||
if constexpr (interruptible<t>)
|
||||
{
|
||||
std::size_t idx = 0u;
|
||||
for (auto & aw : aws)
|
||||
if (cancel[idx])
|
||||
static_cast<t>(aw).interrupt_await();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static detail::fork await_impl(awaitable & this_, std::size_t idx)
|
||||
try
|
||||
{
|
||||
auto & aw = *std::next(std::begin(this_.aws), idx);
|
||||
auto rd = aw.await_ready();
|
||||
if (!rd)
|
||||
{
|
||||
this_.cancel[idx] = &this_.cancel_[idx];
|
||||
co_await this_.cancel[idx]->slot();
|
||||
co_await detail::fork::wired_up;
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
co_await aw;
|
||||
else
|
||||
this_.result[idx].emplace(co_await aw);
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_void_v<decltype(aw.await_resume())>)
|
||||
aw.await_resume();
|
||||
else
|
||||
this_.result[idx].emplace(aw.await_resume());
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
if (!this_.error)
|
||||
this_.error = std::current_exception();
|
||||
this_.cancel_all();
|
||||
}
|
||||
|
||||
detail::fork last_forked;
|
||||
std::size_t last_index = 0u;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
while (last_index < cancel.size())
|
||||
{
|
||||
last_forked = await_impl(*this, last_index++);
|
||||
if (!last_forked.done())
|
||||
return false; // one coro didn't immediately complete!
|
||||
}
|
||||
last_forked.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<typename H>
|
||||
auto await_suspend(
|
||||
std::coroutine_handle<H> h
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
, const boost::source_location & loc = BOOST_CURRENT_LOCATION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
this->loc = loc;
|
||||
#endif
|
||||
exec = &detail::get_executor(h);
|
||||
|
||||
last_forked.release().resume();
|
||||
while (last_index < cancel.size())
|
||||
await_impl(*this, last_index++).release();
|
||||
|
||||
if (error)
|
||||
cancel_all();
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume right away.
|
||||
return false;
|
||||
|
||||
// arm the cancel
|
||||
assign_cancellation(
|
||||
h,
|
||||
[&](asio::cancellation_type ct)
|
||||
{
|
||||
for (auto cs : cancel)
|
||||
if (cs)
|
||||
cs->emit(ct);
|
||||
});
|
||||
|
||||
|
||||
this->coro.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto await_resume(const as_tuple_tag & )
|
||||
{
|
||||
#if defined(BOOST_COBALT_NO_PMR)
|
||||
std::vector<result_type> rr;
|
||||
#else
|
||||
pmr::vector<result_type> rr{this_thread::get_allocator()};
|
||||
#endif
|
||||
|
||||
if (error)
|
||||
return std::make_tuple(error, rr);
|
||||
if constexpr (!std::is_void_v<result_type>)
|
||||
{
|
||||
rr.reserve(result.size());
|
||||
for (auto & t : result)
|
||||
rr.push_back(*std::move(t));
|
||||
return std::make_tuple(std::exception_ptr(), std::move(rr));
|
||||
}
|
||||
}
|
||||
|
||||
auto await_resume(const as_result_tag & )
|
||||
{
|
||||
#if defined(BOOST_COBALT_NO_PMR)
|
||||
std::vector<result_type> rr;
|
||||
#else
|
||||
pmr::vector<result_type> rr{this_thread::get_allocator()};
|
||||
#endif
|
||||
|
||||
if (error)
|
||||
return system::result<decltype(rr), std::exception_ptr>(error);
|
||||
if constexpr (!std::is_void_v<result_type>)
|
||||
{
|
||||
rr.reserve(result.size());
|
||||
for (auto & t : result)
|
||||
rr.push_back(*std::move(t));
|
||||
return rr;
|
||||
}
|
||||
}
|
||||
|
||||
#if _MSC_VER
|
||||
BOOST_NOINLINE
|
||||
#endif
|
||||
auto await_resume()
|
||||
{
|
||||
if (error)
|
||||
std::rethrow_exception(error);
|
||||
if constexpr (!std::is_void_v<result_type>)
|
||||
{
|
||||
#if defined(BOOST_COBALT_NO_PMR)
|
||||
std::vector<result_type> rr;
|
||||
#else
|
||||
pmr::vector<result_type> rr{this_thread::get_allocator()};
|
||||
#endif
|
||||
rr.reserve(result.size());
|
||||
for (auto & t : result)
|
||||
rr.push_back(*std::move(t));
|
||||
return rr;
|
||||
}
|
||||
}
|
||||
};
|
||||
awaitable operator co_await() && {return awaitable{aws};}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_JOIN_HPP
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2023 Klemens D. Morgenstern
|
||||
//
|
||||
// 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 BOOST_COBALT_DETAIL_LEAF_HPP
|
||||
#define BOOST_COBALT_DETAIL_LEAF_HPP
|
||||
|
||||
#include <boost/cobalt/detail/await_result_helper.hpp>
|
||||
#include <boost/leaf/config.hpp>
|
||||
#include <boost/leaf/capture.hpp>
|
||||
#include <boost/leaf/handle_errors.hpp>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
template<typename Awaitable, typename ... H>
|
||||
struct [[nodiscard]] try_catch_awaitable
|
||||
{
|
||||
Awaitable aw;
|
||||
std::tuple<H...> handler;
|
||||
|
||||
bool await_ready() {return aw.await_ready(); }
|
||||
template<typename Promise>
|
||||
auto await_suspend(std::coroutine_handle<Promise> h) {return aw.await_suspend(h);}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
return std::apply(
|
||||
[this](auto && ... h)
|
||||
{
|
||||
return leaf::try_catch(
|
||||
[this]{return std::move(aw).await_resume();},
|
||||
std::move(h)...);
|
||||
}, std::move(handler));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Awaitable, typename ... H>
|
||||
struct [[nodiscard]] try_handle_all_awaitable
|
||||
{
|
||||
Awaitable aw;
|
||||
std::tuple<H...> handler;
|
||||
|
||||
bool await_ready() {return aw.await_ready(); }
|
||||
template<typename Promise>
|
||||
auto await_suspend(std::coroutine_handle<Promise> h) {return aw.await_suspend(h);}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
return std::apply(
|
||||
[this](auto && ... h)
|
||||
{
|
||||
return leaf::try_handle_all(
|
||||
[this]{return std::move(aw).await_resume();},
|
||||
std::move(h)...);
|
||||
}, std::move(handler));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename Awaitable, typename ... H>
|
||||
struct [[nodiscard]] try_handle_some_awaitable
|
||||
{
|
||||
Awaitable aw;
|
||||
std::tuple<H...> handler;
|
||||
|
||||
bool await_ready() {return aw.await_ready(); }
|
||||
template<typename Promise>
|
||||
auto await_suspend(std::coroutine_handle<Promise> h) {return aw.await_suspend(h);}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
return std::apply(
|
||||
[this](auto && ... h)
|
||||
{
|
||||
return leaf::try_handle_some(
|
||||
[this]{return std::move(aw).await_resume();},
|
||||
std::move(h)...);
|
||||
}, std::move(handler));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_LEAF_HPP
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_DETAIL_COBALT_MAIN_HPP
|
||||
#define BOOST_DETAIL_COBALT_MAIN_HPP
|
||||
|
||||
#include <boost/cobalt/main.hpp>
|
||||
#include <boost/cobalt/this_coro.hpp>
|
||||
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
|
||||
namespace boost::asio
|
||||
{
|
||||
|
||||
template<typename Executor>
|
||||
class basic_signal_set;
|
||||
|
||||
}
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int main(int argc, char * argv[]);
|
||||
}
|
||||
|
||||
struct signal_helper
|
||||
{
|
||||
asio::cancellation_signal signal;
|
||||
};
|
||||
|
||||
struct main_promise : signal_helper,
|
||||
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
||||
promise_throw_if_cancelled_base,
|
||||
enable_awaitables<main_promise>,
|
||||
enable_await_allocator<main_promise>,
|
||||
enable_await_executor<main_promise>
|
||||
{
|
||||
main_promise(int, char **) : promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>(
|
||||
signal_helper::signal.slot(), asio::enable_total_cancellation())
|
||||
{
|
||||
[[maybe_unused]] volatile auto p = &detail::main;
|
||||
}
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
inline static pmr::memory_resource * my_resource = pmr::get_default_resource();
|
||||
void * operator new(const std::size_t size)
|
||||
{
|
||||
return my_resource->allocate(size);
|
||||
}
|
||||
|
||||
void operator delete(void * raw, const std::size_t size)
|
||||
{
|
||||
return my_resource->deallocate(raw, size);
|
||||
}
|
||||
#endif
|
||||
std::suspend_always initial_suspend() {return {};}
|
||||
|
||||
BOOST_COBALT_DECL
|
||||
auto final_suspend() noexcept -> std::suspend_never;
|
||||
|
||||
void unhandled_exception() { throw ; }
|
||||
void return_value(int res = 0)
|
||||
{
|
||||
if (result)
|
||||
*result = res;
|
||||
}
|
||||
|
||||
friend auto ::co_main (int argc, char * argv[]) -> boost::cobalt::main;
|
||||
BOOST_COBALT_DECL
|
||||
static int run_main( ::boost::cobalt::main mn);
|
||||
|
||||
friend int main(int argc, char * argv[])
|
||||
{
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::unsynchronized_pool_resource root_resource;
|
||||
struct reset_res
|
||||
{
|
||||
void operator()(pmr::memory_resource * res)
|
||||
{
|
||||
this_thread::set_default_resource(res);
|
||||
}
|
||||
};
|
||||
std::unique_ptr<pmr::memory_resource, reset_res> pr{
|
||||
boost::cobalt::this_thread::set_default_resource(&root_resource)};
|
||||
char buffer[8096];
|
||||
pmr::monotonic_buffer_resource main_res{buffer, 8096, &root_resource};
|
||||
my_resource = &main_res;
|
||||
#endif
|
||||
return run_main(co_main(argc, argv));
|
||||
}
|
||||
|
||||
using executor_type = executor;
|
||||
const executor_type & get_executor() const {return *exec_;}
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
using allocator_type = pmr::polymorphic_allocator<void>;
|
||||
using resource_type = pmr::unsynchronized_pool_resource;
|
||||
|
||||
mutable resource_type resource{my_resource};
|
||||
allocator_type get_allocator() const { return allocator_type(&resource); }
|
||||
#endif
|
||||
|
||||
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
||||
using promise_throw_if_cancelled_base::await_transform;
|
||||
using enable_awaitables<main_promise>::await_transform;
|
||||
using enable_await_allocator<main_promise>::await_transform;
|
||||
using enable_await_executor<main_promise>::await_transform;
|
||||
|
||||
private:
|
||||
int * result;
|
||||
std::optional<asio::executor_work_guard<executor_type>> exec;
|
||||
std::optional<executor_type> exec_;
|
||||
asio::basic_signal_set<executor_type> * signal_set;
|
||||
::boost::cobalt::main get_return_object()
|
||||
{
|
||||
return ::boost::cobalt::main{this};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template<typename Char>
|
||||
struct coroutine_traits<boost::cobalt::main, int, Char>
|
||||
{
|
||||
using promise_type = boost::cobalt::detail::main_promise;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_DETAIL_COBALT_MAIN_HPP
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// based on boost.json
|
||||
//
|
||||
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
|
||||
// Copyright (c) 2020 Krystian Stasiowski (sdkrystian@gmail.com)
|
||||
// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_MONOTONIC_BUFFER_RESOURCE_HPP
|
||||
#define BOOST_COBALT_DETAIL_MONOTONIC_BUFFER_RESOURCE_HPP
|
||||
|
||||
#include <boost/cobalt/config.hpp>
|
||||
#include <new>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct monotonic_resource
|
||||
{
|
||||
private:
|
||||
struct block_
|
||||
{
|
||||
void* p;
|
||||
std::size_t avail;
|
||||
std::size_t size;
|
||||
std::align_val_t aligned;
|
||||
block_* next;
|
||||
|
||||
};
|
||||
|
||||
block_ buffer_;
|
||||
block_* head_ = &buffer_;
|
||||
std::size_t chunk_size_{buffer_.size};
|
||||
public:
|
||||
constexpr monotonic_resource(void * buffer, std::size_t size)
|
||||
: buffer_{buffer, size, size, std::align_val_t{0u}, nullptr} {}
|
||||
|
||||
constexpr monotonic_resource(std::size_t chunk_size = 1024)
|
||||
: buffer_{nullptr, 0u, 0u, std::align_val_t{0u}, nullptr}, chunk_size_(chunk_size) {}
|
||||
|
||||
|
||||
monotonic_resource(monotonic_resource && lhs) noexcept = delete;
|
||||
constexpr ~monotonic_resource()
|
||||
{
|
||||
if (head_ != &buffer_)
|
||||
release();
|
||||
}
|
||||
constexpr void release()
|
||||
{
|
||||
head_ = &buffer_;
|
||||
auto nx = buffer_.next;
|
||||
head_->next = nullptr;
|
||||
head_->avail = head_->size;
|
||||
while (nx != nullptr)
|
||||
{
|
||||
auto p = nx;
|
||||
nx = nx->next;
|
||||
#if defined(__cpp_sized_deallocation)
|
||||
const auto size = sizeof(block_) + p->size;
|
||||
operator delete(p->p, size, p->aligned);
|
||||
#else
|
||||
operator delete(p->p, p->aligned);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void * allocate(std::size_t size, std::align_val_t align_ = std::align_val_t(alignof(std::max_align_t)))
|
||||
{
|
||||
const auto align = (std::max)(static_cast<std::size_t>(align_), alignof(block_));
|
||||
// let's say size = 11, and align is 8, that leaves us with 3
|
||||
{
|
||||
const auto align_offset = size % align ;
|
||||
// padding is 5
|
||||
const auto padding = align - align_offset;
|
||||
const auto needed_size = size + padding;
|
||||
if (needed_size <= head_->avail) // fits, but we need to check alignment too
|
||||
{
|
||||
const auto offset = head_->size - head_->avail;
|
||||
auto pp = static_cast<char*>(head_->p) + offset + padding;
|
||||
head_->avail -= needed_size;
|
||||
return pp; // done
|
||||
}
|
||||
}
|
||||
|
||||
// alright, we need to alloc something.
|
||||
const auto mem_size = (std::max)(chunk_size_, size);
|
||||
// add padding at the end
|
||||
const auto offset = (mem_size % alignof(block_));
|
||||
const auto padding = offset == 0 ? 0u : (alignof(block_) - offset);
|
||||
// size to allocate
|
||||
const auto raw_size = mem_size + padding;
|
||||
const auto alloc_size = raw_size + sizeof(block_);
|
||||
|
||||
const auto aligned = std::align_val_t(align);
|
||||
const auto mem = ::operator new(alloc_size, aligned);
|
||||
const auto block_location = static_cast<char*>(mem) + mem_size + offset;
|
||||
head_ = head_->next = new (block_location) block_{mem, raw_size - size, raw_size, aligned, nullptr};
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct monotonic_allocator
|
||||
{
|
||||
template<typename U>
|
||||
monotonic_allocator(monotonic_allocator<U> alloc) : resource_(alloc.resource_)
|
||||
{
|
||||
|
||||
}
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using propagate_on_container_move_assignment = std::true_type;
|
||||
|
||||
[[nodiscard]] constexpr T* allocate( std::size_t n )
|
||||
{
|
||||
if (resource_)
|
||||
return static_cast<T*>(
|
||||
resource_->allocate(
|
||||
sizeof(T) * n,
|
||||
std::align_val_t(alignof(T))));
|
||||
else
|
||||
return std::allocator<T>().allocate(n);
|
||||
}
|
||||
|
||||
constexpr void deallocate( T* p, std::size_t n )
|
||||
{
|
||||
if (!resource_)
|
||||
std::allocator<T>().deallocate(p, n);
|
||||
}
|
||||
monotonic_allocator(monotonic_resource * resource = nullptr) : resource_(resource) {}
|
||||
private:
|
||||
template<typename>
|
||||
friend struct monotonic_allocator;
|
||||
|
||||
monotonic_resource * resource_{nullptr};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_MONOTONIC_BUFFER_RESOURCE_HPP
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_PROMISE_HPP
|
||||
#define BOOST_COBALT_DETAIL_PROMISE_HPP
|
||||
|
||||
#include <boost/cobalt/detail/exception.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/wrapper.hpp>
|
||||
#include <boost/cobalt/detail/this_thread.hpp>
|
||||
#include <boost/cobalt/unique_handle.hpp>
|
||||
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
|
||||
|
||||
|
||||
#include <boost/core/exchange.hpp>
|
||||
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
struct as_tuple_tag;
|
||||
struct as_result_tag;
|
||||
|
||||
template<typename Return>
|
||||
struct promise;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct promise_receiver;
|
||||
|
||||
template<typename T>
|
||||
struct promise_value_holder
|
||||
{
|
||||
std::optional<T> result;
|
||||
bool result_taken = false;
|
||||
|
||||
system::result<T, std::exception_ptr> get_result_value()
|
||||
{
|
||||
result_taken = true;
|
||||
BOOST_ASSERT(result);
|
||||
return {system::in_place_value, std::move(*result)};
|
||||
}
|
||||
|
||||
void return_value(T && ret)
|
||||
{
|
||||
result.emplace(std::move(ret));
|
||||
static_cast<promise_receiver<T>*>(this)->set_done();
|
||||
}
|
||||
|
||||
void return_value(const T & ret)
|
||||
{
|
||||
result.emplace(ret);
|
||||
static_cast<promise_receiver<T>*>(this)->set_done();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<>
|
||||
struct promise_value_holder<void>
|
||||
{
|
||||
bool result_taken = false;
|
||||
system::result<void, std::exception_ptr> get_result_value()
|
||||
{
|
||||
result_taken = true;
|
||||
return {system::in_place_value};
|
||||
}
|
||||
|
||||
inline void return_void();
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct promise_receiver : promise_value_holder<T>
|
||||
{
|
||||
std::exception_ptr exception;
|
||||
system::result<T, std::exception_ptr> get_result()
|
||||
{
|
||||
if (exception && !done) // detached error
|
||||
return {system::in_place_error, std::exchange(exception, nullptr)};
|
||||
else if (exception)
|
||||
{
|
||||
this->result_taken = true;
|
||||
return {system::in_place_error, exception};
|
||||
}
|
||||
return this->get_result_value();
|
||||
}
|
||||
void unhandled_exception()
|
||||
{
|
||||
exception = std::current_exception();
|
||||
set_done();
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
unique_handle<void> awaited_from{nullptr};
|
||||
|
||||
void set_done()
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
promise_receiver() = default;
|
||||
promise_receiver(promise_receiver && lhs) noexcept
|
||||
: promise_value_holder<T>(std::move(lhs)),
|
||||
exception(std::move(lhs.exception)), done(lhs.done), awaited_from(std::move(lhs.awaited_from)),
|
||||
reference(lhs.reference), cancel_signal(lhs.cancel_signal)
|
||||
{
|
||||
if (!done && !exception)
|
||||
{
|
||||
reference = this;
|
||||
lhs.exception = moved_from_exception();
|
||||
}
|
||||
|
||||
lhs.done = true;
|
||||
|
||||
}
|
||||
|
||||
~promise_receiver()
|
||||
{
|
||||
if (!done && reference == this)
|
||||
reference = nullptr;
|
||||
}
|
||||
|
||||
promise_receiver(promise_receiver * &reference, asio::cancellation_signal & cancel_signal)
|
||||
: reference(reference), cancel_signal(cancel_signal)
|
||||
{
|
||||
reference = this;
|
||||
}
|
||||
|
||||
struct awaitable
|
||||
{
|
||||
promise_receiver * self;
|
||||
std::exception_ptr ex;
|
||||
asio::cancellation_slot cl;
|
||||
|
||||
awaitable(promise_receiver * self) : self(self)
|
||||
{
|
||||
}
|
||||
|
||||
awaitable(awaitable && aw) : self(aw.self)
|
||||
{
|
||||
}
|
||||
|
||||
~awaitable ()
|
||||
{
|
||||
}
|
||||
bool await_ready() const { return self->done; }
|
||||
|
||||
template<typename Promise>
|
||||
bool await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
if (self->done) // ok, so we're actually done already, so noop
|
||||
return false;
|
||||
|
||||
if (ex)
|
||||
return false;
|
||||
|
||||
if (self->awaited_from != nullptr) // we're already being awaited, that's an error!
|
||||
{
|
||||
ex = already_awaited();
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (requires (Promise p) {p.get_cancellation_slot();})
|
||||
if ((cl = h.promise().get_cancellation_slot()).is_connected())
|
||||
cl.emplace<forward_cancellation>(self->cancel_signal);
|
||||
|
||||
self->awaited_from.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
T await_resume(const boost::source_location & loc = BOOST_CURRENT_LOCATION)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
if (ex)
|
||||
std::rethrow_exception(ex);
|
||||
return self->get_result().value(loc);
|
||||
}
|
||||
|
||||
system::result<T, std::exception_ptr> await_resume(const as_result_tag &)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
if (ex)
|
||||
return {system::in_place_error, std::move(ex)};
|
||||
return self->get_result();
|
||||
}
|
||||
|
||||
auto await_resume(const as_tuple_tag &)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
|
||||
if constexpr (std::is_void_v<T>)
|
||||
{
|
||||
if (ex)
|
||||
return std::move(ex);
|
||||
return self->get_result().error();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ex)
|
||||
return std::make_tuple(std::move(ex), T{});
|
||||
auto res = self->get_result();
|
||||
if (res.has_error())
|
||||
return std::make_tuple(res.error(), T{});
|
||||
else
|
||||
return std::make_tuple(std::exception_ptr(), std::move(*res));
|
||||
}
|
||||
}
|
||||
void interrupt_await() &
|
||||
{
|
||||
if (!self)
|
||||
return ;
|
||||
ex = detached_exception();
|
||||
if (self->awaited_from)
|
||||
self->awaited_from.release().resume();
|
||||
}
|
||||
};
|
||||
|
||||
promise_receiver * &reference;
|
||||
asio::cancellation_signal & cancel_signal;
|
||||
|
||||
awaitable get_awaitable() {return awaitable{this};}
|
||||
|
||||
|
||||
void interrupt_await() &
|
||||
{
|
||||
exception = detached_exception();
|
||||
awaited_from.release().resume();
|
||||
}
|
||||
};
|
||||
|
||||
inline void promise_value_holder<void>::return_void()
|
||||
{
|
||||
static_cast<promise_receiver<void>*>(this)->set_done();
|
||||
}
|
||||
|
||||
template<typename Return>
|
||||
struct cobalt_promise_result
|
||||
{
|
||||
promise_receiver<Return>* receiver{nullptr};
|
||||
void return_value(Return && ret)
|
||||
{
|
||||
if(receiver)
|
||||
receiver->return_value(std::move(ret));
|
||||
}
|
||||
|
||||
void return_value(const Return & ret)
|
||||
{
|
||||
if(receiver)
|
||||
receiver->return_value(ret);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<>
|
||||
struct cobalt_promise_result<void>
|
||||
{
|
||||
promise_receiver<void>* receiver{nullptr};
|
||||
void return_void()
|
||||
{
|
||||
if(receiver)
|
||||
receiver->return_void();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Return>
|
||||
struct cobalt_promise
|
||||
: promise_memory_resource_base,
|
||||
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
||||
promise_throw_if_cancelled_base,
|
||||
enable_awaitables<cobalt_promise<Return>>,
|
||||
enable_await_allocator<cobalt_promise<Return>>,
|
||||
enable_await_executor<cobalt_promise<Return>>,
|
||||
cobalt_promise_result<Return>
|
||||
{
|
||||
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
||||
using promise_throw_if_cancelled_base::await_transform;
|
||||
using enable_awaitables<cobalt_promise<Return>>::await_transform;
|
||||
using enable_await_allocator<cobalt_promise<Return>>::await_transform;
|
||||
using enable_await_executor<cobalt_promise<Return>>::await_transform;
|
||||
|
||||
[[nodiscard]] promise<Return> get_return_object()
|
||||
{
|
||||
return promise<Return>{this};
|
||||
}
|
||||
|
||||
mutable asio::cancellation_signal signal;
|
||||
|
||||
using executor_type = executor;
|
||||
executor_type exec;
|
||||
const executor_type & get_executor() const {return exec;}
|
||||
|
||||
template<typename ... Args>
|
||||
cobalt_promise(Args & ...args)
|
||||
:
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
promise_memory_resource_base(detail::get_memory_resource_from_args(args...)),
|
||||
#endif
|
||||
exec{detail::get_executor_from_args(args...)}
|
||||
{
|
||||
this->reset_cancellation_source(signal.slot());
|
||||
}
|
||||
|
||||
std::suspend_never initial_suspend() {return {};}
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
return final_awaitable{this};
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
if (this->receiver)
|
||||
this->receiver->unhandled_exception();
|
||||
else
|
||||
throw ;
|
||||
}
|
||||
|
||||
~cobalt_promise()
|
||||
{
|
||||
if (this->receiver)
|
||||
{
|
||||
if (!this->receiver->done && !this->receiver->exception)
|
||||
this->receiver->exception = completed_unexpected();
|
||||
this->receiver->set_done();
|
||||
this->receiver->awaited_from.reset(nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
private:
|
||||
struct final_awaitable
|
||||
{
|
||||
cobalt_promise * promise;
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return promise->receiver && promise->receiver->awaited_from.get() == nullptr;
|
||||
}
|
||||
|
||||
std::coroutine_handle<void> await_suspend(std::coroutine_handle<cobalt_promise> h) noexcept
|
||||
{
|
||||
std::coroutine_handle<void> res = std::noop_coroutine();
|
||||
if (promise->receiver && promise->receiver->awaited_from.get() != nullptr)
|
||||
res = promise->receiver->awaited_from.release();
|
||||
|
||||
|
||||
if (auto &rec = h.promise().receiver; rec != nullptr)
|
||||
{
|
||||
if (!rec->done && !rec->exception)
|
||||
rec->exception = completed_unexpected();
|
||||
rec->set_done();
|
||||
rec->awaited_from.reset(nullptr);
|
||||
rec = nullptr;
|
||||
}
|
||||
detail::self_destroy(h);
|
||||
return res;
|
||||
}
|
||||
|
||||
void await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_PROMISE_HPP
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_RACE_HPP
|
||||
#define BOOST_COBALT_DETAIL_RACE_HPP
|
||||
|
||||
#include <boost/cobalt/detail/await_result_helper.hpp>
|
||||
#include <boost/cobalt/detail/fork.hpp>
|
||||
#include <boost/cobalt/detail/handler.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/result.hpp>
|
||||
#include <boost/cobalt/this_thread.hpp>
|
||||
#include <boost/cobalt/detail/util.hpp>
|
||||
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
|
||||
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/core/demangle.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct left_race_tag {};
|
||||
|
||||
// helpers it determining the type of things;
|
||||
template<typename Base, // range of aw
|
||||
typename Awaitable = Base>
|
||||
struct race_traits
|
||||
{
|
||||
// for a ranges race this is based on the range, not the AW in it.
|
||||
constexpr static bool is_lvalue = std::is_lvalue_reference_v<Base>;
|
||||
|
||||
// what the value is supposed to be cast to before the co_await_operator
|
||||
using awaitable = std::conditional_t<is_lvalue, std::decay_t<Awaitable> &, Awaitable &&>;
|
||||
|
||||
// do we need operator co_await
|
||||
constexpr static bool is_actual = awaitable_type<awaitable>;
|
||||
|
||||
// the type with .await_ functions & interrupt_await
|
||||
using actual_awaitable
|
||||
= std::conditional_t<
|
||||
is_actual,
|
||||
awaitable,
|
||||
decltype(get_awaitable_type(std::declval<awaitable>()))>;
|
||||
|
||||
// the type to be used with interruptible
|
||||
using interruptible_type
|
||||
= std::conditional_t<
|
||||
std::is_lvalue_reference_v<Base>,
|
||||
std::decay_t<actual_awaitable> &,
|
||||
std::decay_t<actual_awaitable> &&>;
|
||||
|
||||
constexpr static bool interruptible =
|
||||
cobalt::interruptible<interruptible_type>;
|
||||
|
||||
static void do_interrupt(std::decay_t<actual_awaitable> & aw)
|
||||
{
|
||||
if constexpr (interruptible)
|
||||
static_cast<interruptible_type>(aw).interrupt_await();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct interruptible_base
|
||||
{
|
||||
virtual void interrupt_await() = 0;
|
||||
};
|
||||
|
||||
template<asio::cancellation_type Ct, typename URBG, typename ... Args>
|
||||
struct race_variadic_impl
|
||||
{
|
||||
|
||||
template<typename URBG_>
|
||||
race_variadic_impl(URBG_ && g, Args && ... args)
|
||||
: args{std::forward<Args>(args)...}, g(std::forward<URBG_>(g))
|
||||
{
|
||||
}
|
||||
|
||||
std::tuple<Args...> args;
|
||||
URBG g;
|
||||
|
||||
constexpr static std::size_t tuple_size = sizeof...(Args);
|
||||
|
||||
struct awaitable : fork::static_shared_state<256 * tuple_size>
|
||||
{
|
||||
|
||||
#if !defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
boost::source_location loc;
|
||||
#endif
|
||||
|
||||
template<std::size_t ... Idx>
|
||||
awaitable(std::tuple<Args...> & args, URBG & g, std::index_sequence<Idx...>) :
|
||||
aws{args}
|
||||
{
|
||||
if constexpr (!std::is_same_v<URBG, left_race_tag>)
|
||||
std::shuffle(impls.begin(), impls.end(), g);
|
||||
std::fill(working.begin(), working.end(), nullptr);
|
||||
}
|
||||
|
||||
std::tuple<Args...> & aws;
|
||||
std::array<asio::cancellation_signal, tuple_size> cancel_;
|
||||
|
||||
template<typename > constexpr static auto make_null() {return nullptr;};
|
||||
std::array<asio::cancellation_signal*, tuple_size> cancel = {make_null<Args>()...};
|
||||
|
||||
std::array<interruptible_base*, tuple_size> working;
|
||||
|
||||
std::size_t index{std::numeric_limits<std::size_t>::max()};
|
||||
|
||||
constexpr static bool all_void = (std::is_void_v<co_await_result_t<Args>> && ... );
|
||||
std::optional<variant2::variant<void_as_monostate<co_await_result_t<Args>>...>> result;
|
||||
std::exception_ptr error;
|
||||
|
||||
bool has_result() const
|
||||
{
|
||||
return index != std::numeric_limits<std::size_t>::max();
|
||||
}
|
||||
|
||||
void cancel_all()
|
||||
{
|
||||
interrupt_await();
|
||||
for (auto i = 0u; i < tuple_size; i++)
|
||||
if (auto &r = cancel[i]; r)
|
||||
std::exchange(r, nullptr)->emit(Ct);
|
||||
}
|
||||
|
||||
void interrupt_await()
|
||||
{
|
||||
for (auto i : working)
|
||||
if (i)
|
||||
i->interrupt_await();
|
||||
}
|
||||
|
||||
template<typename T, typename Error>
|
||||
void assign_error(system::result<T, Error> & res)
|
||||
try
|
||||
{
|
||||
std::move(res).value(loc);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
error = std::current_exception();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_error(system::result<T, std::exception_ptr> & res)
|
||||
{
|
||||
error = std::move(res).error();
|
||||
}
|
||||
|
||||
template<std::size_t Idx>
|
||||
static detail::fork await_impl(awaitable & this_)
|
||||
try
|
||||
{
|
||||
using traits = race_traits<mp11::mp_at_c<mp11::mp_list<Args...>, Idx>>;
|
||||
|
||||
typename traits::actual_awaitable aw_{
|
||||
get_awaitable_type(
|
||||
static_cast<typename traits::awaitable>(std::get<Idx>(this_.aws))
|
||||
)
|
||||
};
|
||||
|
||||
as_result_t aw{aw_};
|
||||
|
||||
|
||||
struct interruptor final : interruptible_base
|
||||
{
|
||||
std::decay_t<typename traits::actual_awaitable> & aw;
|
||||
interruptor(std::decay_t<typename traits::actual_awaitable> & aw) : aw(aw) {}
|
||||
void interrupt_await() override
|
||||
{
|
||||
traits::do_interrupt(aw);
|
||||
}
|
||||
};
|
||||
interruptor in{aw_};
|
||||
//if constexpr (traits::interruptible)
|
||||
this_.working[Idx] = ∈
|
||||
|
||||
auto transaction = [&this_, idx = Idx] {
|
||||
if (this_.has_result())
|
||||
boost::throw_exception(std::runtime_error("Another transaction already started"));
|
||||
this_.cancel[idx] = nullptr;
|
||||
// reserve the index early bc
|
||||
this_.index = idx;
|
||||
this_.cancel_all();
|
||||
};
|
||||
|
||||
co_await fork::set_transaction_function(transaction);
|
||||
// check manually if we're ready
|
||||
auto rd = aw.await_ready();
|
||||
if (!rd)
|
||||
{
|
||||
this_.cancel[Idx] = &this_.cancel_[Idx];
|
||||
co_await this_.cancel[Idx]->slot();
|
||||
// make sure the executor is set
|
||||
co_await detail::fork::wired_up;
|
||||
|
||||
// do the await - this doesn't call await-ready again
|
||||
if constexpr (std::is_void_v<decltype(aw_.await_resume())>)
|
||||
{
|
||||
auto res = co_await aw;
|
||||
if (!this_.has_result())
|
||||
{
|
||||
this_.index = Idx;
|
||||
if (res.has_error())
|
||||
this_.assign_error(res);
|
||||
}
|
||||
if constexpr(!all_void)
|
||||
if (this_.index == Idx && !res.has_error())
|
||||
this_.result.emplace(variant2::in_place_index<Idx>);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto val = co_await aw;
|
||||
if (!this_.has_result())
|
||||
this_.index = Idx;
|
||||
if (this_.index == Idx)
|
||||
{
|
||||
if (val.has_error())
|
||||
this_.assign_error(val);
|
||||
else
|
||||
this_.result.emplace(variant2::in_place_index<Idx>, *std::move(val));
|
||||
}
|
||||
}
|
||||
this_.cancel[Idx] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this_.has_result())
|
||||
this_.index = Idx;
|
||||
if constexpr (std::is_void_v<decltype(aw_.await_resume())>)
|
||||
{
|
||||
auto res = aw.await_resume();
|
||||
if (this_.index == Idx)
|
||||
{
|
||||
if (res.has_error())
|
||||
this_.assign_error(res);
|
||||
else
|
||||
this_.result.emplace(variant2::in_place_index<Idx>);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this_.index == Idx)
|
||||
{
|
||||
auto res = aw.await_resume();
|
||||
if (res.has_error())
|
||||
this_.assign_error(res);
|
||||
else
|
||||
this_.result.emplace(variant2::in_place_index<Idx>, *std::move(res));
|
||||
}
|
||||
else
|
||||
aw.await_resume();
|
||||
}
|
||||
this_.cancel[Idx] = nullptr;
|
||||
}
|
||||
this_.cancel_all();
|
||||
this_.working[Idx] = nullptr;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
if (!this_.has_result())
|
||||
this_.index = Idx;
|
||||
if (this_.index == Idx)
|
||||
this_.error = std::current_exception();
|
||||
this_.working[Idx] = nullptr;
|
||||
}
|
||||
|
||||
std::array<detail::fork(*)(awaitable&), tuple_size> impls {
|
||||
[]<std::size_t ... Idx>(std::index_sequence<Idx...>)
|
||||
{
|
||||
return std::array<detail::fork(*)(awaitable&), tuple_size>{&await_impl<Idx>...};
|
||||
}(std::make_index_sequence<tuple_size>{})
|
||||
};
|
||||
|
||||
detail::fork last_forked;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
last_forked = impls[0](*this);
|
||||
return last_forked.done();
|
||||
}
|
||||
|
||||
template<typename H>
|
||||
auto await_suspend(
|
||||
std::coroutine_handle<H> h,
|
||||
const boost::source_location & loc = BOOST_CURRENT_LOCATION)
|
||||
{
|
||||
this->loc = loc;
|
||||
|
||||
this->exec = &cobalt::detail::get_executor(h);
|
||||
last_forked.release().resume();
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
for (std::size_t idx = 1u;
|
||||
idx < tuple_size; idx++) // we'
|
||||
{
|
||||
auto l = impls[idx](*this);
|
||||
const auto d = l.done();
|
||||
l.release();
|
||||
if (d)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
// arm the cancel
|
||||
assign_cancellation(
|
||||
h,
|
||||
[&](asio::cancellation_type ct)
|
||||
{
|
||||
for (auto & cs : cancel)
|
||||
if (cs)
|
||||
cs->emit(ct);
|
||||
});
|
||||
|
||||
this->coro.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
#if _MSC_VER
|
||||
BOOST_NOINLINE
|
||||
#endif
|
||||
auto await_resume()
|
||||
{
|
||||
if (error)
|
||||
std::rethrow_exception(error);
|
||||
if constexpr (all_void)
|
||||
return index;
|
||||
else
|
||||
return std::move(*result);
|
||||
}
|
||||
|
||||
auto await_resume(const as_tuple_tag &)
|
||||
{
|
||||
if constexpr (all_void)
|
||||
return std::make_tuple(error, index);
|
||||
else
|
||||
return std::make_tuple(error, std::move(*result));
|
||||
}
|
||||
|
||||
auto await_resume(const as_result_tag & )
|
||||
-> system::result<std::conditional_t<all_void, std::size_t, variant2::variant<void_as_monostate<co_await_result_t<Args>>...>>, std::exception_ptr>
|
||||
{
|
||||
if (error)
|
||||
return {system::in_place_error, error};
|
||||
if constexpr (all_void)
|
||||
return {system::in_place_value, index};
|
||||
else
|
||||
return {system::in_place_value, std::move(*result)};
|
||||
}
|
||||
};
|
||||
awaitable operator co_await() &&
|
||||
{
|
||||
return awaitable{args, g, std::make_index_sequence<tuple_size>{}};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<asio::cancellation_type Ct, typename URBG, typename Range>
|
||||
struct race_ranged_impl
|
||||
{
|
||||
|
||||
using result_type = co_await_result_t<std::decay_t<decltype(*std::begin(std::declval<Range>()))>>;
|
||||
template<typename URBG_>
|
||||
race_ranged_impl(URBG_ && g, Range && rng)
|
||||
: range{std::forward<Range>(rng)}, g(std::forward<URBG_>(g))
|
||||
{
|
||||
}
|
||||
|
||||
Range range;
|
||||
URBG g;
|
||||
|
||||
struct awaitable : fork::shared_state
|
||||
{
|
||||
|
||||
#if !defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
|
||||
boost::source_location loc;
|
||||
#endif
|
||||
|
||||
using type = std::decay_t<decltype(*std::begin(std::declval<Range>()))>;
|
||||
using traits = race_traits<Range, type>;
|
||||
|
||||
std::size_t index{std::numeric_limits<std::size_t>::max()};
|
||||
|
||||
std::conditional_t<
|
||||
std::is_void_v<result_type>,
|
||||
variant2::monostate,
|
||||
std::optional<result_type>> result;
|
||||
|
||||
std::exception_ptr error;
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::monotonic_buffer_resource res;
|
||||
pmr::polymorphic_allocator<void> alloc{&resource};
|
||||
|
||||
Range &aws;
|
||||
|
||||
struct dummy
|
||||
{
|
||||
template<typename ... Args>
|
||||
dummy(Args && ...) {}
|
||||
};
|
||||
|
||||
std::conditional_t<traits::interruptible,
|
||||
pmr::vector<std::decay_t<typename traits::actual_awaitable>*>,
|
||||
dummy> working{std::size(aws), alloc};
|
||||
|
||||
/* all below `reorder` is reordered
|
||||
*
|
||||
* cancel[idx] is for aws[reorder[idx]]
|
||||
*/
|
||||
pmr::vector<std::size_t> reorder{std::size(aws), alloc};
|
||||
pmr::vector<asio::cancellation_signal> cancel_{std::size(aws), alloc};
|
||||
pmr::vector<asio::cancellation_signal*> cancel{std::size(aws), alloc};
|
||||
|
||||
#else
|
||||
Range &aws;
|
||||
|
||||
struct dummy
|
||||
{
|
||||
template<typename ... Args>
|
||||
dummy(Args && ...) {}
|
||||
};
|
||||
|
||||
std::conditional_t<traits::interruptible,
|
||||
std::vector<std::decay_t<typename traits::actual_awaitable>*>,
|
||||
dummy> working{std::size(aws), std::allocator<void>()};
|
||||
|
||||
/* all below `reorder` is reordered
|
||||
*
|
||||
* cancel[idx] is for aws[reorder[idx]]
|
||||
*/
|
||||
std::vector<std::size_t> reorder{std::size(aws), std::allocator<void>()};
|
||||
std::vector<asio::cancellation_signal> cancel_{std::size(aws), std::allocator<void>()};
|
||||
std::vector<asio::cancellation_signal*> cancel{std::size(aws), std::allocator<void>()};
|
||||
|
||||
#endif
|
||||
|
||||
bool has_result() const {return index != std::numeric_limits<std::size_t>::max(); }
|
||||
|
||||
|
||||
awaitable(Range & aws, URBG & g)
|
||||
: fork::shared_state((256 + sizeof(co_awaitable_type<type>) + sizeof(std::size_t)) * std::size(aws))
|
||||
, aws(aws)
|
||||
{
|
||||
std::generate(reorder.begin(), reorder.end(), [i = std::size_t(0u)]() mutable {return i++;});
|
||||
if constexpr (traits::interruptible)
|
||||
std::fill(working.begin(), working.end(), nullptr);
|
||||
if constexpr (!std::is_same_v<URBG, left_race_tag>)
|
||||
std::shuffle(reorder.begin(), reorder.end(), g);
|
||||
}
|
||||
|
||||
void cancel_all()
|
||||
{
|
||||
interrupt_await();
|
||||
for (auto & r : cancel)
|
||||
if (r)
|
||||
std::exchange(r, nullptr)->emit(Ct);
|
||||
}
|
||||
void interrupt_await()
|
||||
{
|
||||
if constexpr (traits::interruptible)
|
||||
for (auto aw : working)
|
||||
if (aw)
|
||||
traits::do_interrupt(*aw);
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename Error>
|
||||
void assign_error(system::result<T, Error> & res)
|
||||
try
|
||||
{
|
||||
std::move(res).value(loc);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
error = std::current_exception();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_error(system::result<T, std::exception_ptr> & res)
|
||||
{
|
||||
error = std::move(res).error();
|
||||
}
|
||||
|
||||
static detail::fork await_impl(awaitable & this_, std::size_t idx)
|
||||
try
|
||||
{
|
||||
typename traits::actual_awaitable aw_{
|
||||
get_awaitable_type(
|
||||
static_cast<typename traits::awaitable>(*std::next(std::begin(this_.aws), idx))
|
||||
)};
|
||||
|
||||
as_result_t aw{aw_};
|
||||
|
||||
if constexpr (traits::interruptible)
|
||||
this_.working[idx] = &aw_;
|
||||
|
||||
auto transaction = [&this_, idx = idx] {
|
||||
if (this_.has_result())
|
||||
boost::throw_exception(std::runtime_error("Another transaction already started"));
|
||||
this_.cancel[idx] = nullptr;
|
||||
// reserve the index early bc
|
||||
this_.index = idx;
|
||||
this_.cancel_all();
|
||||
};
|
||||
|
||||
co_await fork::set_transaction_function(transaction);
|
||||
// check manually if we're ready
|
||||
auto rd = aw.await_ready();
|
||||
if (!rd)
|
||||
{
|
||||
this_.cancel[idx] = &this_.cancel_[idx];
|
||||
co_await this_.cancel[idx]->slot();
|
||||
// make sure the executor is set
|
||||
co_await detail::fork::wired_up;
|
||||
|
||||
// do the await - this doesn't call await-ready again
|
||||
if constexpr (std::is_void_v<result_type>)
|
||||
{
|
||||
auto res = co_await aw;
|
||||
if (!this_.has_result())
|
||||
{
|
||||
if (res.has_error())
|
||||
this_.assign_error(res);
|
||||
this_.index = idx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto val = co_await aw;
|
||||
if (!this_.has_result())
|
||||
this_.index = idx;
|
||||
if (this_.index == idx)
|
||||
{
|
||||
if (val.has_error())
|
||||
this_.assign_error(val);
|
||||
else
|
||||
this_.result.emplace(*std::move(val));
|
||||
}
|
||||
}
|
||||
this_.cancel[idx] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!this_.has_result())
|
||||
this_.index = idx;
|
||||
if constexpr (std::is_void_v<decltype(aw_.await_resume())>)
|
||||
{
|
||||
auto val = aw.await_resume();
|
||||
if (val.has_error())
|
||||
this_.assign_error(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this_.index == idx)
|
||||
{
|
||||
auto val = aw.await_resume();
|
||||
if (val.has_error())
|
||||
this_.assign_error(val);
|
||||
else
|
||||
this_.result.emplace(*std::move(val));
|
||||
}
|
||||
else
|
||||
aw.await_resume();
|
||||
}
|
||||
this_.cancel[idx] = nullptr;
|
||||
}
|
||||
this_.cancel_all();
|
||||
if constexpr (traits::interruptible)
|
||||
this_.working[idx] = nullptr;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
if (!this_.has_result())
|
||||
this_.index = idx;
|
||||
if (this_.index == idx)
|
||||
this_.error = std::current_exception();
|
||||
if constexpr (traits::interruptible)
|
||||
this_.working[idx] = nullptr;
|
||||
}
|
||||
|
||||
detail::fork last_forked;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
last_forked = await_impl(*this, reorder.front());
|
||||
return last_forked.done();
|
||||
}
|
||||
|
||||
template<typename H>
|
||||
auto await_suspend(std::coroutine_handle<H> h,
|
||||
const boost::source_location & loc = BOOST_CURRENT_LOCATION)
|
||||
{
|
||||
this->loc = loc;
|
||||
this->exec = &detail::get_executor(h);
|
||||
last_forked.release().resume();
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
for (auto itr = std::next(reorder.begin());
|
||||
itr < reorder.end(); std::advance(itr, 1)) // we'
|
||||
{
|
||||
auto l = await_impl(*this, *itr);
|
||||
auto d = l.done();
|
||||
l.release();
|
||||
if (d)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this->outstanding_work()) // already done, resume rightaway.
|
||||
return false;
|
||||
|
||||
// arm the cancel
|
||||
assign_cancellation(
|
||||
h,
|
||||
[&](asio::cancellation_type ct)
|
||||
{
|
||||
for (auto & cs : cancel)
|
||||
if (cs)
|
||||
cs->emit(ct);
|
||||
});
|
||||
|
||||
this->coro.reset(h.address());
|
||||
return true;
|
||||
}
|
||||
|
||||
#if _MSC_VER
|
||||
BOOST_NOINLINE
|
||||
#endif
|
||||
auto await_resume()
|
||||
{
|
||||
if (error)
|
||||
std::rethrow_exception(error);
|
||||
if constexpr (std::is_void_v<result_type>)
|
||||
return index;
|
||||
else
|
||||
return std::make_pair(index, *result);
|
||||
}
|
||||
|
||||
auto await_resume(const as_tuple_tag &)
|
||||
{
|
||||
if constexpr (std::is_void_v<result_type>)
|
||||
return std::make_tuple(error, index);
|
||||
else
|
||||
return std::make_tuple(error, std::make_pair(index, std::move(*result)));
|
||||
}
|
||||
|
||||
auto await_resume(const as_result_tag & )
|
||||
-> system::result<result_type, std::exception_ptr>
|
||||
{
|
||||
if (error)
|
||||
return {system::in_place_error, error};
|
||||
if constexpr (std::is_void_v<result_type>)
|
||||
return {system::in_place_value, index};
|
||||
else
|
||||
return {system::in_place_value, std::make_pair(index, std::move(*result))};
|
||||
}
|
||||
|
||||
};
|
||||
awaitable operator co_await() &&
|
||||
{
|
||||
return awaitable{range, g};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_RACE_HPP
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_SBO_BUFFER_RESOURCE_HPP
|
||||
#define BOOST_COBALT_DETAIL_SBO_BUFFER_RESOURCE_HPP
|
||||
|
||||
#include <boost/cobalt/config.hpp>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct sbo_resource
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
final : pmr::memory_resource
|
||||
#endif
|
||||
{
|
||||
private:
|
||||
struct block_
|
||||
{
|
||||
void* p{nullptr};
|
||||
std::size_t avail{0u};
|
||||
std::size_t size{0u};
|
||||
bool fragmented{false};
|
||||
};
|
||||
|
||||
block_ buffer_;
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
pmr::memory_resource * upstream_;
|
||||
#endif
|
||||
constexpr std::size_t align_as_max_(std::size_t size)
|
||||
{
|
||||
auto diff = size % alignof(std::max_align_t );
|
||||
if (diff > 0)
|
||||
return size + alignof(std::max_align_t) - diff;
|
||||
else
|
||||
return size;
|
||||
}
|
||||
constexpr void align_as_max_()
|
||||
{
|
||||
const auto buffer = static_cast<char*>(buffer_.p) - static_cast<char*>(nullptr);
|
||||
const auto diff = buffer % alignof(std::max_align_t );
|
||||
if (diff > 0)
|
||||
{
|
||||
const auto padding = alignof(std::max_align_t) - diff;
|
||||
buffer_.p = static_cast<void*>(static_cast<char*>(nullptr) + buffer + padding);
|
||||
if (padding >= buffer_.size) [[unlikely]]
|
||||
{
|
||||
buffer_.size = 0;
|
||||
buffer_.avail = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer_.size -= padding;
|
||||
buffer_.avail -= padding;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr sbo_resource(void * buffer, std::size_t size
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
, pmr::memory_resource * upstream = pmr::get_default_resource()
|
||||
#endif
|
||||
) : buffer_{buffer, size, size, false}
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
, upstream_(upstream)
|
||||
#endif
|
||||
{
|
||||
align_as_max_();
|
||||
}
|
||||
|
||||
#if defined(BOOST_COBALT_NO_PMR)
|
||||
constexpr sbo_resource() : buffer_{nullptr, 0u, 0u, false} {}
|
||||
|
||||
#else
|
||||
constexpr sbo_resource(pmr::memory_resource * upstream = pmr::get_default_resource())
|
||||
: buffer_{nullptr, 0u, 0u, false}, upstream_(upstream) {}
|
||||
#endif
|
||||
|
||||
~sbo_resource() = default;
|
||||
|
||||
constexpr void * do_allocate(std::size_t size, std::size_t align)
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
override
|
||||
#endif
|
||||
{
|
||||
const auto sz = align_as_max_(size);
|
||||
if (sz <= buffer_.avail && !buffer_.fragmented) [[likely]]
|
||||
{
|
||||
auto p = static_cast<char*>(buffer_.p) + buffer_.size - buffer_.avail;
|
||||
buffer_.avail -= sz;
|
||||
return p;
|
||||
}
|
||||
else
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
return upstream_->allocate(size, align);
|
||||
#else
|
||||
return operator new(size, std::align_val_t(align));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
constexpr void do_deallocate(void * p, std::size_t size, std::size_t align)
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
override
|
||||
#endif
|
||||
{
|
||||
auto begin = static_cast<char*>(static_cast<char*>(buffer_.p));
|
||||
auto end = begin + buffer_.size;
|
||||
auto itr = static_cast<char*>(p);
|
||||
|
||||
if(begin <= itr && itr < end) [[likely]]
|
||||
{
|
||||
const auto sz = align_as_max_(size);
|
||||
const auto used_mem_end = end - buffer_.avail;
|
||||
const auto dealloc_end = itr + sz;
|
||||
if (used_mem_end != dealloc_end )
|
||||
buffer_.fragmented = true;
|
||||
buffer_.avail += sz;
|
||||
if (buffer_.avail == buffer_.size)
|
||||
buffer_.fragmented = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
upstream_->deallocate(p, size, align);
|
||||
#else
|
||||
#if defined(__cpp_sized_deallocation)
|
||||
operator delete(p, size, std::align_val_t(align));
|
||||
#else
|
||||
operator delete(p, std::align_val_t(align));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
constexpr bool do_is_equal(memory_resource const& other) const noexcept override
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
inline sbo_resource * get_null_sbo_resource()
|
||||
{
|
||||
static sbo_resource empty_resource;
|
||||
return &empty_resource;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct sbo_allocator
|
||||
{
|
||||
template<typename U>
|
||||
sbo_allocator(sbo_allocator<U> alloc) : resource_(alloc.resource_)
|
||||
{
|
||||
|
||||
}
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using propagate_on_container_move_assignment = std::true_type;
|
||||
|
||||
[[nodiscard]] constexpr T* allocate( std::size_t n )
|
||||
{
|
||||
BOOST_ASSERT(resource_);
|
||||
return static_cast<T*>(resource_->do_allocate(sizeof(T) * n, alignof(T)));
|
||||
}
|
||||
|
||||
constexpr void deallocate( T* p, std::size_t n )
|
||||
{
|
||||
BOOST_ASSERT(resource_);
|
||||
resource_->do_deallocate(p, sizeof(T) * n, alignof(T));
|
||||
}
|
||||
sbo_allocator(sbo_resource * resource) : resource_(resource) {}
|
||||
private:
|
||||
template<typename>
|
||||
friend struct sbo_allocator;
|
||||
|
||||
sbo_resource * resource_{nullptr};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_SBO_BUFFER_RESOURCE_HPP
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_SPAWN_HPP
|
||||
#define BOOST_COBALT_DETAIL_SPAWN_HPP
|
||||
|
||||
#include <boost/cobalt/task.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
|
||||
#include <boost/smart_ptr/allocate_unique.hpp>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
template<typename T>
|
||||
struct task;
|
||||
}
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct async_initiate_spawn
|
||||
{
|
||||
template<typename Handler, typename T>
|
||||
void operator()(Handler && h, task<T> a, executor exec)
|
||||
{
|
||||
auto & rec = a.receiver_;
|
||||
if (rec.done)
|
||||
return asio::dispatch(
|
||||
asio::get_associated_immediate_executor(h, exec),
|
||||
asio::append(std::forward<Handler>(h), rec.exception, rec.exception ? T() : *rec.get_result()));
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
auto dalloc = pmr::polymorphic_allocator<void>{boost::cobalt::this_thread::get_default_resource()};
|
||||
auto alloc = asio::get_associated_allocator(h, dalloc);
|
||||
#else
|
||||
auto alloc = asio::get_associated_allocator(h);
|
||||
#endif
|
||||
auto recs = allocate_unique<detail::task_receiver<T>>(alloc, std::move(rec));
|
||||
|
||||
auto sl = asio::get_associated_cancellation_slot(h);
|
||||
if (sl.is_connected())
|
||||
sl.template emplace<detail::forward_dispatch_cancellation>(recs->promise->signal, exec);
|
||||
|
||||
auto p = recs.get();
|
||||
|
||||
p->promise->exec.emplace(exec);
|
||||
p->promise->exec_ = exec;
|
||||
|
||||
struct completion_handler
|
||||
{
|
||||
using allocator_type = std::decay_t<decltype(alloc)>;
|
||||
|
||||
allocator_type get_allocator() const { return alloc_; }
|
||||
allocator_type alloc_;
|
||||
|
||||
using executor_type = std::decay_t<decltype(asio::get_associated_executor(h, exec))>;
|
||||
const executor_type &get_executor() const { return exec_; }
|
||||
executor_type exec_;
|
||||
|
||||
decltype(recs) r;
|
||||
Handler handler;
|
||||
|
||||
void operator()()
|
||||
{
|
||||
auto ex = r->exception;
|
||||
T rr{};
|
||||
if (r->result)
|
||||
rr = std::move(*r->result);
|
||||
r.reset();
|
||||
std::move(handler)(ex, std::move(rr));
|
||||
}
|
||||
};
|
||||
|
||||
p->awaited_from.reset(detail::post_coroutine(
|
||||
completion_handler{
|
||||
alloc, asio::get_associated_executor(h, exec), std::move(recs), std::move(h)
|
||||
}).address());
|
||||
|
||||
asio::dispatch(exec, std::coroutine_handle<detail::task_promise<T>>::from_promise(*p->promise));
|
||||
}
|
||||
|
||||
template<typename Handler>
|
||||
void operator()(Handler && h, task<void> a, executor exec)
|
||||
{
|
||||
if (a.receiver_.done)
|
||||
return asio::dispatch(
|
||||
asio::get_associated_immediate_executor(h, exec),
|
||||
asio::append(std::forward<Handler>(h), a.receiver_.exception));
|
||||
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
auto alloc = asio::get_associated_allocator(h, pmr::polymorphic_allocator<void>{boost::cobalt::this_thread::get_default_resource()});
|
||||
#else
|
||||
auto alloc = asio::get_associated_allocator(h);
|
||||
#endif
|
||||
auto recs = allocate_unique<detail::task_receiver<void>>(alloc, std::move(a.receiver_));
|
||||
|
||||
if (recs->done)
|
||||
return asio::dispatch(asio::get_associated_immediate_executor(h, exec),
|
||||
asio::append(std::forward<Handler>(h), recs->exception));
|
||||
|
||||
auto sl = asio::get_associated_cancellation_slot(h);
|
||||
if (sl.is_connected())
|
||||
sl.template emplace<detail::forward_dispatch_cancellation>(recs->promise->signal, exec);
|
||||
|
||||
auto p = recs.get();
|
||||
|
||||
p->promise->exec.emplace(exec);
|
||||
p->promise->exec_ = exec;
|
||||
|
||||
struct completion_handler
|
||||
{
|
||||
using allocator_type = std::decay_t<decltype(alloc)>;
|
||||
|
||||
const allocator_type &get_allocator() const { return alloc_; }
|
||||
|
||||
allocator_type alloc_;
|
||||
|
||||
using executor_type = std::decay_t<decltype(asio::get_associated_executor(h, exec))>;
|
||||
const executor_type &get_executor() const { return exec_; }
|
||||
|
||||
executor_type exec_;
|
||||
decltype(recs) r;
|
||||
Handler handler;
|
||||
|
||||
void operator()()
|
||||
{
|
||||
auto ex = r->exception;
|
||||
r.reset();
|
||||
std::move(handler)(ex);
|
||||
}
|
||||
};
|
||||
|
||||
p->awaited_from.reset(detail::post_coroutine(completion_handler{
|
||||
alloc, asio::get_associated_executor(h, exec), std::move(recs), std::forward<Handler>(h)
|
||||
}).address());
|
||||
|
||||
asio::dispatch(exec, std::coroutine_handle<detail::task_promise<void>>::from_promise(*p->promise));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_SPAWN_HPP
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_TASK_HPP
|
||||
#define BOOST_COBALT_DETAIL_TASK_HPP
|
||||
|
||||
#include <boost/cobalt/detail/exception.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/wrapper.hpp>
|
||||
#include <boost/cobalt/detail/this_thread.hpp>
|
||||
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
struct as_tuple_tag;
|
||||
struct as_result_tag;
|
||||
|
||||
template<typename Return>
|
||||
struct task;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct task_receiver;
|
||||
|
||||
template<typename T>
|
||||
struct task_value_holder
|
||||
{
|
||||
std::optional<T> result;
|
||||
bool result_taken = false;
|
||||
|
||||
system::result<T, std::exception_ptr> get_result_value()
|
||||
{
|
||||
result_taken = true;
|
||||
BOOST_ASSERT(result);
|
||||
return {system::in_place_value, std::move(*result)};
|
||||
}
|
||||
|
||||
void return_value(T && ret)
|
||||
{
|
||||
result.emplace(std::move(ret));
|
||||
static_cast<task_receiver<T>*>(this)->set_done();
|
||||
}
|
||||
void return_value(const T & ret)
|
||||
{
|
||||
result.emplace(ret);
|
||||
static_cast<task_receiver<T>*>(this)->set_done();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct task_value_holder<void>
|
||||
{
|
||||
bool result_taken = false;
|
||||
system::result<void, std::exception_ptr> get_result_value()
|
||||
{
|
||||
result_taken = true;
|
||||
return {system::in_place_value};
|
||||
}
|
||||
|
||||
inline void return_void();
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct task_promise;
|
||||
|
||||
template<typename T>
|
||||
struct task_receiver : task_value_holder<T>
|
||||
{
|
||||
std::exception_ptr exception;
|
||||
system::result<T, std::exception_ptr> get_result()
|
||||
{
|
||||
if (exception && !done) // detached error
|
||||
return {system::in_place_error, std::exchange(exception, nullptr)};
|
||||
else if (exception)
|
||||
{
|
||||
this->result_taken = true;
|
||||
return {system::in_place_error, exception};
|
||||
}
|
||||
return this->get_result_value();
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
exception = std::current_exception();
|
||||
set_done();
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
unique_handle<void> awaited_from{nullptr};
|
||||
|
||||
void set_done()
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
task_receiver() = default;
|
||||
task_receiver(task_receiver && lhs)
|
||||
: task_value_holder<T>(std::move(lhs)),
|
||||
exception(std::move(lhs.exception)), done(lhs.done), awaited_from(std::move(lhs.awaited_from)),
|
||||
promise(lhs.promise)
|
||||
{
|
||||
if (!done && !exception)
|
||||
{
|
||||
promise->receiver = this;
|
||||
lhs.exception = moved_from_exception();
|
||||
}
|
||||
|
||||
lhs.done = true;
|
||||
}
|
||||
|
||||
~task_receiver()
|
||||
{
|
||||
if (!done && promise && promise->receiver == this)
|
||||
{
|
||||
promise->receiver = nullptr;
|
||||
if (!promise->started)
|
||||
std::coroutine_handle<task_promise<T>>::from_promise(*promise).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
task_receiver(task_promise<T> * promise)
|
||||
: promise(promise)
|
||||
{
|
||||
promise->receiver = this;
|
||||
}
|
||||
|
||||
struct awaitable
|
||||
{
|
||||
task_receiver * self;
|
||||
asio::cancellation_slot cl;
|
||||
awaitable(task_receiver * self) : self(self)
|
||||
{
|
||||
}
|
||||
|
||||
awaitable(awaitable && aw) : self(aw.self)
|
||||
{
|
||||
}
|
||||
|
||||
~awaitable ()
|
||||
{
|
||||
}
|
||||
|
||||
bool await_ready() const { return self->done; }
|
||||
|
||||
template<typename Promise>
|
||||
BOOST_NOINLINE std::coroutine_handle<void> await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
if (self->done) // ok, so we're actually done already, so noop
|
||||
return std::coroutine_handle<void>::from_address(h.address());
|
||||
|
||||
if constexpr (requires (Promise p) {p.get_cancellation_slot();})
|
||||
if ((cl = h.promise().get_cancellation_slot()).is_connected())
|
||||
cl.emplace<forward_cancellation>(self->promise->signal);
|
||||
|
||||
|
||||
if constexpr (requires (Promise p) {p.get_executor();})
|
||||
self->promise->exec.emplace(h.promise().get_executor());
|
||||
else
|
||||
self->promise->exec.emplace(this_thread::get_executor());
|
||||
self->promise->exec_ = self->promise->exec->get_executor();
|
||||
self->awaited_from.reset(h.address());
|
||||
|
||||
return std::coroutine_handle<task_promise<T>>::from_promise(*self->promise);
|
||||
}
|
||||
|
||||
T await_resume(const boost::source_location & loc = BOOST_CURRENT_LOCATION)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
|
||||
return self->get_result().value(loc);
|
||||
}
|
||||
|
||||
system::result<T, std::exception_ptr> await_resume(const as_result_tag &)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
return self->get_result();
|
||||
}
|
||||
|
||||
auto await_resume(const as_tuple_tag &)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
auto res = self->get_result();
|
||||
if constexpr (std::is_void_v<T>)
|
||||
return res.error();
|
||||
else
|
||||
{
|
||||
if (res.has_error())
|
||||
return std::make_tuple(res.error(), T{});
|
||||
else
|
||||
return std::make_tuple(std::exception_ptr(), std::move(*res));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void interrupt_await() &
|
||||
{
|
||||
if (!self)
|
||||
return ;
|
||||
self->exception = detached_exception();
|
||||
if (self->awaited_from)
|
||||
self->awaited_from.release().resume();
|
||||
}
|
||||
};
|
||||
|
||||
task_promise<T> * promise;
|
||||
|
||||
awaitable get_awaitable() {return awaitable{this};}
|
||||
|
||||
|
||||
void interrupt_await() &
|
||||
{
|
||||
exception = detached_exception();
|
||||
awaited_from.release().resume();
|
||||
}
|
||||
};
|
||||
|
||||
inline void task_value_holder<void>::return_void()
|
||||
{
|
||||
static_cast<task_receiver<void>*>(this)->set_done();
|
||||
}
|
||||
|
||||
template<typename Return>
|
||||
struct task_promise_result
|
||||
{
|
||||
task_receiver<Return>* receiver{nullptr};
|
||||
void return_value(Return && ret)
|
||||
{
|
||||
if(receiver)
|
||||
receiver->return_value(std::move(ret));
|
||||
}
|
||||
void return_value(const Return & ret)
|
||||
{
|
||||
if(receiver)
|
||||
receiver->return_value(ret);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct task_promise_result<void>
|
||||
{
|
||||
task_receiver<void>* receiver{nullptr};
|
||||
void return_void()
|
||||
{
|
||||
if(receiver)
|
||||
receiver->return_void();
|
||||
}
|
||||
};
|
||||
|
||||
struct async_initiate_spawn;
|
||||
|
||||
template<typename Return>
|
||||
struct task_promise
|
||||
: promise_memory_resource_base,
|
||||
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
||||
promise_throw_if_cancelled_base,
|
||||
enable_awaitables<task_promise<Return>>,
|
||||
enable_await_allocator<task_promise<Return>>,
|
||||
enable_await_executor<task_promise<Return>>,
|
||||
task_promise_result<Return>
|
||||
{
|
||||
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
||||
using promise_throw_if_cancelled_base::await_transform;
|
||||
using enable_awaitables<task_promise<Return>>::await_transform;
|
||||
using enable_await_allocator<task_promise<Return>>::await_transform;
|
||||
using enable_await_executor<task_promise<Return>>::await_transform;
|
||||
|
||||
[[nodiscard]] task<Return> get_return_object()
|
||||
{
|
||||
return task<Return>{this};
|
||||
}
|
||||
|
||||
mutable asio::cancellation_signal signal;
|
||||
|
||||
using executor_type = executor;
|
||||
std::optional<asio::executor_work_guard<executor_type>> exec;
|
||||
std::optional<executor_type> exec_;
|
||||
const executor_type & get_executor() const
|
||||
{
|
||||
if (!exec)
|
||||
throw_exception(asio::bad_executor());
|
||||
BOOST_ASSERT(exec_);
|
||||
return *exec_;
|
||||
}
|
||||
|
||||
template<typename ... Args>
|
||||
task_promise(Args & ...args)
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
: promise_memory_resource_base(detail::get_memory_resource_from_args_global(args...))
|
||||
#endif
|
||||
{
|
||||
this->reset_cancellation_source(signal.slot());
|
||||
}
|
||||
|
||||
struct initial_awaitable
|
||||
{
|
||||
task_promise * promise;
|
||||
|
||||
bool await_ready() const noexcept {return false;}
|
||||
void await_suspend(std::coroutine_handle<>) {}
|
||||
|
||||
void await_resume()
|
||||
{
|
||||
promise->started = true;
|
||||
}
|
||||
};
|
||||
|
||||
auto initial_suspend()
|
||||
{
|
||||
|
||||
return initial_awaitable{this};
|
||||
}
|
||||
|
||||
struct final_awaitable
|
||||
{
|
||||
task_promise * promise;
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return promise->receiver && promise->receiver->awaited_from.get() == nullptr;
|
||||
}
|
||||
|
||||
BOOST_NOINLINE
|
||||
auto await_suspend(std::coroutine_handle<task_promise> h) noexcept
|
||||
{
|
||||
std::coroutine_handle<void> res = std::noop_coroutine();
|
||||
if (promise->receiver && promise->receiver->awaited_from.get() != nullptr)
|
||||
res = promise->receiver->awaited_from.release();
|
||||
|
||||
|
||||
if (auto & rec = h.promise().receiver; rec != nullptr)
|
||||
{
|
||||
if (!rec->done && !rec->exception)
|
||||
rec->exception = completed_unexpected();
|
||||
rec->set_done();
|
||||
rec->awaited_from.reset(nullptr);
|
||||
rec = nullptr;
|
||||
}
|
||||
detail::self_destroy(h);
|
||||
return res;
|
||||
}
|
||||
|
||||
void await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
return final_awaitable{this};
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
if (this->receiver)
|
||||
this->receiver->unhandled_exception();
|
||||
else
|
||||
throw ;
|
||||
}
|
||||
|
||||
~task_promise()
|
||||
{
|
||||
if (this->receiver)
|
||||
{
|
||||
if (!this->receiver->done && !this->receiver->exception)
|
||||
this->receiver->exception = completed_unexpected();
|
||||
this->receiver->set_done();
|
||||
this->receiver->awaited_from.reset(nullptr);
|
||||
}
|
||||
}
|
||||
bool started = false;
|
||||
friend struct async_initiate;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_TASK_HPP
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_THIS_THREAD_HPP
|
||||
#define BOOST_COBALT_DETAIL_THIS_THREAD_HPP
|
||||
|
||||
#include <boost/cobalt/this_thread.hpp>
|
||||
|
||||
#include <boost/asio/uses_executor.hpp>
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
inline executor
|
||||
extract_executor(executor exec) { return exec; }
|
||||
|
||||
#if defined(BOOST_COBALT_CUSTOM_EXECUTOR) || defined(BOOST_COBALT_USE_IO_CONTEXT)
|
||||
BOOST_COBALT_DECL executor
|
||||
extract_executor(asio::any_io_executor exec);
|
||||
#endif
|
||||
|
||||
template<typename ... Args>
|
||||
executor get_executor_from_args(Args &&... args)
|
||||
{
|
||||
using args_type = mp11::mp_list<std::decay_t<Args>...>;
|
||||
constexpr static auto I = mp11::mp_find<args_type, asio::executor_arg_t>::value;
|
||||
if constexpr (sizeof...(Args) == I)
|
||||
return this_thread::get_executor();
|
||||
else //
|
||||
return extract_executor(std::get<I + 1u>(std::tie(args...)));
|
||||
}
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
template<typename ... Args>
|
||||
pmr::memory_resource * get_memory_resource_from_args(Args &&... args)
|
||||
{
|
||||
using args_type = mp11::mp_list<std::decay_t<Args>...>;
|
||||
constexpr static auto I = mp11::mp_find<args_type, std::allocator_arg_t>::value;
|
||||
if constexpr (sizeof...(Args) == I)
|
||||
return this_thread::get_default_resource();
|
||||
else //
|
||||
return std::get<I + 1u>(std::tie(args...)).resource();
|
||||
}
|
||||
|
||||
template<typename ... Args>
|
||||
pmr::memory_resource * get_memory_resource_from_args_global(Args &&... args)
|
||||
{
|
||||
using args_type = mp11::mp_list<std::decay_t<Args>...>;
|
||||
constexpr static auto I = mp11::mp_find<args_type, std::allocator_arg_t>::value;
|
||||
if constexpr (sizeof...(Args) == I)
|
||||
return pmr::get_default_resource();
|
||||
else //
|
||||
return std::get<I + 1u>(std::tie(args...)).resource();
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_THIS_THREAD_HPP
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_THREAD_HPP
|
||||
#define BOOST_COBALT_DETAIL_THREAD_HPP
|
||||
|
||||
#include <boost/cobalt/config.hpp>
|
||||
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
||||
#include <boost/cobalt/detail/handler.hpp>
|
||||
#include <boost/cobalt/concepts.hpp>
|
||||
#include <boost/cobalt/this_coro.hpp>
|
||||
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace boost::cobalt
|
||||
{
|
||||
|
||||
struct as_tuple_tag;
|
||||
struct as_result_tag;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct thread_promise;
|
||||
}
|
||||
|
||||
struct thread;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
|
||||
struct signal_helper_2
|
||||
{
|
||||
asio::cancellation_signal signal;
|
||||
};
|
||||
|
||||
|
||||
struct thread_state
|
||||
{
|
||||
asio::io_context ctx{1u};
|
||||
asio::cancellation_signal signal;
|
||||
std::mutex mtx;
|
||||
std::optional<completion_handler<std::exception_ptr>> waitor;
|
||||
std::atomic<bool> done = false;
|
||||
};
|
||||
|
||||
struct thread_promise : signal_helper_2,
|
||||
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
||||
promise_throw_if_cancelled_base,
|
||||
enable_awaitables<thread_promise>,
|
||||
enable_await_allocator<thread_promise>,
|
||||
enable_await_executor<thread_promise>
|
||||
{
|
||||
BOOST_COBALT_DECL thread_promise();
|
||||
|
||||
struct initial_awaitable
|
||||
{
|
||||
bool await_ready() const {return false;}
|
||||
void await_suspend(std::coroutine_handle<thread_promise> h)
|
||||
{
|
||||
h.promise().mtx.unlock();
|
||||
}
|
||||
|
||||
void await_resume() {}
|
||||
};
|
||||
|
||||
auto initial_suspend() noexcept
|
||||
{
|
||||
return initial_awaitable{};
|
||||
}
|
||||
std::suspend_never final_suspend() noexcept
|
||||
{
|
||||
wexec_.reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
void unhandled_exception() { throw; }
|
||||
void return_void() { }
|
||||
|
||||
using executor_type = typename cobalt::executor;
|
||||
const executor_type & get_executor() const {return *exec_;}
|
||||
|
||||
#if !defined(BOOST_COBALT_NO_PMR)
|
||||
using allocator_type = pmr::polymorphic_allocator<void>;
|
||||
using resource_type = pmr::unsynchronized_pool_resource;
|
||||
|
||||
resource_type * resource;
|
||||
allocator_type get_allocator() const { return allocator_type(resource); }
|
||||
#endif
|
||||
|
||||
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
||||
using promise_throw_if_cancelled_base::await_transform;
|
||||
using enable_awaitables<thread_promise>::await_transform;
|
||||
using enable_await_allocator<thread_promise>::await_transform;
|
||||
using enable_await_executor<thread_promise>::await_transform;
|
||||
|
||||
BOOST_COBALT_DECL
|
||||
boost::cobalt::thread get_return_object();
|
||||
|
||||
void set_executor(asio::io_context::executor_type exec)
|
||||
{
|
||||
wexec_.emplace(exec);
|
||||
exec_.emplace(exec);
|
||||
}
|
||||
|
||||
std::mutex mtx;
|
||||
private:
|
||||
|
||||
std::optional<asio::executor_work_guard<asio::io_context::executor_type>> wexec_;
|
||||
std::optional<cobalt::executor> exec_;
|
||||
};
|
||||
|
||||
struct thread_awaitable
|
||||
{
|
||||
asio::cancellation_slot cl;
|
||||
std::optional<std::tuple<std::exception_ptr>> res;
|
||||
bool await_ready(const boost::source_location & loc = BOOST_CURRENT_LOCATION) const
|
||||
{
|
||||
if (state_ == nullptr)
|
||||
boost::throw_exception(std::invalid_argument("Thread expired"), loc);
|
||||
std::lock_guard<std::mutex> lock{state_->mtx};
|
||||
return state_->done;
|
||||
}
|
||||
|
||||
template<typename Promise>
|
||||
bool await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
BOOST_ASSERT(state_);
|
||||
|
||||
std::lock_guard<std::mutex> lock{state_->mtx};
|
||||
if (state_->done)
|
||||
return false;
|
||||
|
||||
if constexpr (requires (Promise p) {p.get_cancellation_slot();})
|
||||
if ((cl = h.promise().get_cancellation_slot()).is_connected())
|
||||
{
|
||||
cl.assign(
|
||||
[st = state_](asio::cancellation_type type)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{st->mtx};
|
||||
asio::post(st->ctx,
|
||||
[st, type]
|
||||
{
|
||||
BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
|
||||
st->signal.emit(type);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
state_->waitor.emplace(h, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
void await_resume()
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
if (thread_)
|
||||
thread_->join();
|
||||
if (!res) // await_ready
|
||||
return;
|
||||
if (auto ee = std::get<0>(*res))
|
||||
std::rethrow_exception(ee);
|
||||
}
|
||||
|
||||
system::result<void, std::exception_ptr> await_resume(const as_result_tag &)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
if (thread_)
|
||||
thread_->join();
|
||||
if (!res) // await_ready
|
||||
return {system::in_place_value};
|
||||
if (auto ee = std::get<0>(*res))
|
||||
return {system::in_place_error, std::move(ee)};
|
||||
|
||||
return {system::in_place_value};
|
||||
}
|
||||
|
||||
std::tuple<std::exception_ptr> await_resume(const as_tuple_tag &)
|
||||
{
|
||||
if (cl.is_connected())
|
||||
cl.clear();
|
||||
if (thread_)
|
||||
thread_->join();
|
||||
|
||||
return std::get<0>(*res);
|
||||
}
|
||||
|
||||
explicit thread_awaitable(std::shared_ptr<detail::thread_state> state)
|
||||
: state_(std::move(state)) {}
|
||||
|
||||
explicit thread_awaitable(std::thread thread,
|
||||
std::shared_ptr<detail::thread_state> state)
|
||||
: thread_(std::move(thread)), state_(std::move(state)) {}
|
||||
private:
|
||||
std::optional<std::thread> thread_;
|
||||
std::shared_ptr<detail::thread_state> state_;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_THREAD_HPP
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// 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 BOOST_COBALT_UTIL_HPP
|
||||
#define BOOST_COBALT_UTIL_HPP
|
||||
|
||||
#include <boost/cobalt/config.hpp>
|
||||
#include <boost/cobalt/this_thread.hpp>
|
||||
|
||||
#include <boost/system/result.hpp>
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <coroutine>
|
||||
|
||||
namespace boost::variant2
|
||||
{
|
||||
struct monostate;
|
||||
}
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
constexpr std::size_t variadic_first(std::size_t = 0u)
|
||||
{
|
||||
return std::numeric_limits<std::size_t>::max();
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename First, typename ... Args>
|
||||
constexpr std::size_t variadic_first(std::size_t pos = 0u)
|
||||
{
|
||||
if constexpr (std::is_same_v<std::decay_t<First>, T>)
|
||||
return pos;
|
||||
else
|
||||
return variadic_first<T, Args...>(pos+1);
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename ... Args>
|
||||
constexpr bool variadic_has = variadic_first<T, Args...>() < sizeof...(Args);
|
||||
|
||||
template<std::size_t Idx, typename First, typename ... Args>
|
||||
requires (Idx <= sizeof...(Args))
|
||||
constexpr decltype(auto) get_variadic(First && first, Args && ... args)
|
||||
{
|
||||
if constexpr (Idx == 0u)
|
||||
return static_cast<First>(first);
|
||||
else
|
||||
return get_variadic<Idx-1u>(static_cast<Args>(args)...);
|
||||
}
|
||||
|
||||
template<std::size_t Idx, typename ... Args>
|
||||
struct variadic_element;
|
||||
|
||||
template<std::size_t Idx, typename First, typename ...Tail>
|
||||
struct variadic_element<Idx, First, Tail...>
|
||||
{
|
||||
using type = typename variadic_element<Idx-1, Tail...>::type;
|
||||
};
|
||||
|
||||
template<typename First, typename ...Tail>
|
||||
struct variadic_element<0u, First, Tail...>
|
||||
{
|
||||
using type = First;
|
||||
};
|
||||
|
||||
template<std::size_t Idx, typename ... Args>
|
||||
using variadic_element_t = typename variadic_element<Idx, Args...>::type;
|
||||
|
||||
|
||||
template<typename ... Args>
|
||||
struct variadic_last
|
||||
{
|
||||
using type = variadic_element_t<sizeof...(Args) - 1, Args...>;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct variadic_last<>
|
||||
{
|
||||
using type = void;
|
||||
};
|
||||
|
||||
template<typename ... Args>
|
||||
using variadic_last_t = typename variadic_last<Args...>::type;
|
||||
|
||||
|
||||
template<typename First>
|
||||
constexpr decltype(auto) get_last_variadic(First && first)
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
template<typename First, typename ... Args>
|
||||
constexpr decltype(auto) get_last_variadic(First &&, Args && ... args)
|
||||
{
|
||||
return get_last_variadic(static_cast<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename Awaitable>
|
||||
auto get_resume_result(Awaitable & aw) -> system::result<decltype(aw.await_resume()), std::exception_ptr>
|
||||
{
|
||||
using type = decltype(aw.await_resume());
|
||||
try
|
||||
{
|
||||
if constexpr (std::is_void_v<type>)
|
||||
{
|
||||
aw.await_resume();
|
||||
return {};
|
||||
}
|
||||
else
|
||||
return aw.await_resume();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
#if BOOST_COBALT_NO_SELF_DELETE
|
||||
|
||||
BOOST_COBALT_DECL
|
||||
void self_destroy(std::coroutine_handle<void> h, const cobalt::executor & exec) noexcept;
|
||||
|
||||
template<typename T>
|
||||
inline void self_destroy(std::coroutine_handle<T> h) noexcept
|
||||
{
|
||||
if constexpr (requires {h.promise().get_executor();})
|
||||
self_destroy(h, h.promise().get_executor());
|
||||
else
|
||||
self_destroy(h, this_thread::get_executor());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template<typename T>
|
||||
inline void self_destroy(std::coroutine_handle<T> h) noexcept
|
||||
{
|
||||
h.destroy();
|
||||
}
|
||||
|
||||
template<typename T, typename Executor>
|
||||
inline void self_destroy(std::coroutine_handle<T> h, const Executor &) noexcept
|
||||
{
|
||||
h.destroy();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
using void_as_monostate = std::conditional_t<std::is_void_v<T>, variant2::monostate, T>;
|
||||
|
||||
template<typename T>
|
||||
using monostate_as_void = std::conditional_t<std::is_same_v<T, variant2::monostate>, void, T>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_UTIL_HPP
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_WAIT_GROUP_HPP
|
||||
#define BOOST_COBALT_DETAIL_WAIT_GROUP_HPP
|
||||
|
||||
#include <boost/cobalt/promise.hpp>
|
||||
#include <boost/cobalt/race.hpp>
|
||||
#include <boost/cobalt/gather.hpp>
|
||||
|
||||
#include <list>
|
||||
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
struct race_wrapper
|
||||
{
|
||||
using impl_type = decltype(race(std::declval<std::list<promise<void>> &>()));
|
||||
std::list<promise<void>> &waitables_;
|
||||
|
||||
race_wrapper(std::list<promise<void>> &waitables) : waitables_(waitables)
|
||||
{
|
||||
}
|
||||
|
||||
struct awaitable_type
|
||||
{
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
if (waitables_.empty())
|
||||
return true;
|
||||
else
|
||||
return impl_->await_ready();
|
||||
}
|
||||
|
||||
template<typename Promise>
|
||||
auto await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
return impl_->await_suspend(h);
|
||||
}
|
||||
|
||||
void await_resume()
|
||||
{
|
||||
if (waitables_.empty())
|
||||
return;
|
||||
auto idx = impl_->await_resume();
|
||||
if (idx != std::numeric_limits<std::size_t>::max())
|
||||
waitables_.erase(std::next(waitables_.begin(), idx));
|
||||
}
|
||||
|
||||
awaitable_type(std::list<promise<void>> &waitables) : waitables_(waitables)
|
||||
{
|
||||
if (!waitables_.empty())
|
||||
impl_.emplace(waitables_, random_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<impl_type::awaitable> impl_;
|
||||
std::list<promise<void>> &waitables_;
|
||||
std::default_random_engine &random_{detail::prng()};
|
||||
|
||||
};
|
||||
awaitable_type operator co_await() &&
|
||||
{
|
||||
return awaitable_type(waitables_);
|
||||
}
|
||||
};
|
||||
|
||||
struct gather_wrapper
|
||||
{
|
||||
using impl_type = decltype(gather(std::declval<std::list<promise<void>> &>()));
|
||||
std::list<promise<void>> &waitables_;
|
||||
|
||||
gather_wrapper(std::list<promise<void>> &waitables) : waitables_(waitables)
|
||||
{
|
||||
}
|
||||
|
||||
struct awaitable_type
|
||||
{
|
||||
bool await_ready()
|
||||
{
|
||||
if (waitables_.empty())
|
||||
return true;
|
||||
else
|
||||
return impl_->await_ready();
|
||||
}
|
||||
|
||||
template<typename Promise>
|
||||
auto await_suspend(std::coroutine_handle<Promise> h)
|
||||
{
|
||||
return impl_->await_suspend(h);
|
||||
}
|
||||
|
||||
void await_resume()
|
||||
{
|
||||
if (waitables_.empty())
|
||||
return;
|
||||
BOOST_ASSERT(impl_);
|
||||
impl_->await_resume();
|
||||
waitables_.clear();
|
||||
}
|
||||
|
||||
awaitable_type(std::list<promise<void>> &waitables) : waitables_(waitables)
|
||||
{
|
||||
if (!waitables_.empty())
|
||||
impl_.emplace(waitables_);
|
||||
}
|
||||
private:
|
||||
std::list<promise<void>> &waitables_;
|
||||
std::optional<decltype(gather(waitables_).operator co_await())> impl_;
|
||||
};
|
||||
|
||||
awaitable_type operator co_await()
|
||||
{
|
||||
return awaitable_type(waitables_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_WAIT_GROUP_HPP
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// 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 BOOST_COBALT_DETAIL_WITH_HPP
|
||||
#define BOOST_COBALT_DETAIL_WITH_HPP
|
||||
|
||||
#include <boost/cobalt/concepts.hpp>
|
||||
#include <boost/cobalt/this_coro.hpp>
|
||||
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct [[nodiscard]] with_impl
|
||||
{
|
||||
struct promise_type;
|
||||
|
||||
bool await_ready() { return false;}
|
||||
|
||||
template<typename Promise>
|
||||
BOOST_NOINLINE auto await_suspend(std::coroutine_handle<Promise> h) -> std::coroutine_handle<promise_type>;
|
||||
inline T await_resume();
|
||||
|
||||
private:
|
||||
with_impl(promise_type & promise) : promise(promise) {}
|
||||
promise_type & promise;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct with_promise_value
|
||||
{
|
||||
std::optional<T> result;
|
||||
|
||||
void return_value(std::optional<T> && value)
|
||||
{
|
||||
if (value) // so non-move-assign types work
|
||||
result.emplace(std::move(*value));
|
||||
}
|
||||
|
||||
T get_result()
|
||||
{
|
||||
return std::move(result).value();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct with_promise_value<void>
|
||||
{
|
||||
void return_void() {}
|
||||
void get_result() {}
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct with_impl<T>::promise_type
|
||||
: with_promise_value<T>,
|
||||
enable_awaitables<promise_type>,
|
||||
enable_await_allocator<promise_type>
|
||||
{
|
||||
using enable_awaitables<promise_type>::await_transform;
|
||||
using enable_await_allocator<promise_type>::await_transform;
|
||||
|
||||
|
||||
using executor_type = executor;
|
||||
const executor_type & get_executor() const {return *exec;}
|
||||
std::optional<executor_type> exec;
|
||||
|
||||
with_impl get_return_object()
|
||||
{
|
||||
return with_impl{*this};
|
||||
}
|
||||
|
||||
std::exception_ptr e;
|
||||
void unhandled_exception()
|
||||
{
|
||||
e = std::current_exception();
|
||||
}
|
||||
|
||||
std::suspend_always initial_suspend() {return {};}
|
||||
|
||||
struct final_awaitable
|
||||
{
|
||||
promise_type *promise;
|
||||
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BOOST_NOINLINE
|
||||
auto await_suspend(std::coroutine_handle<promise_type> h) noexcept -> std::coroutine_handle<void>
|
||||
{
|
||||
return std::coroutine_handle<void>::from_address(h.promise().awaited_from.address());
|
||||
}
|
||||
|
||||
void await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
return final_awaitable{this};
|
||||
}
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
cancellation_slot_type get_cancellation_slot() const {return slot_;}
|
||||
asio::cancellation_slot slot_;
|
||||
|
||||
std::coroutine_handle<void> awaited_from{nullptr};
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
T with_impl<T>::await_resume()
|
||||
{
|
||||
auto e = promise.e;
|
||||
auto res = std::move(promise.get_result());
|
||||
std::coroutine_handle<promise_type>::from_promise(promise).destroy();
|
||||
if (e)
|
||||
std::rethrow_exception(e);
|
||||
|
||||
return std::move(res);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void with_impl<void>::await_resume()
|
||||
{
|
||||
auto e = promise.e;
|
||||
std::coroutine_handle<promise_type>::from_promise(promise).destroy();
|
||||
if (e)
|
||||
std::rethrow_exception(e);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename Promise>
|
||||
auto with_impl<T>::await_suspend(std::coroutine_handle<Promise> h) -> std::coroutine_handle<promise_type>
|
||||
{
|
||||
if constexpr (requires (Promise p) {p.get_executor();})
|
||||
promise.exec.emplace(h.promise().get_executor());
|
||||
else
|
||||
promise.exec.emplace(this_thread::get_executor());
|
||||
|
||||
if constexpr (requires (Promise p) {p.get_cancellation_slot();})
|
||||
promise.slot_ = h.promise().get_cancellation_slot();
|
||||
|
||||
promise.awaited_from = h;
|
||||
return std::coroutine_handle<promise_type>::from_promise(promise);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_DETAIL_WITH_HPP
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// 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 BOOST_COBALT_WRAPPER_HPP
|
||||
#define BOOST_COBALT_WRAPPER_HPP
|
||||
|
||||
#include <boost/cobalt/this_coro.hpp>
|
||||
#include <boost/cobalt/concepts.hpp>
|
||||
#include <boost/cobalt/detail/util.hpp>
|
||||
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/executor.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#include <coroutine>
|
||||
#include <utility>
|
||||
#if BOOST_COBALT_NO_SELF_DELETE
|
||||
#include <boost/asio/consign.hpp>
|
||||
#endif
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
template<typename Allocator>
|
||||
struct partial_promise_base
|
||||
{
|
||||
template<typename CompletionToken>
|
||||
void * operator new(const std::size_t size, CompletionToken & token)
|
||||
{
|
||||
// gcc: 168 40
|
||||
// clang: 144 40
|
||||
return allocate_coroutine(size, asio::get_associated_allocator(token));
|
||||
}
|
||||
|
||||
template<typename Executor, typename CompletionToken>
|
||||
void * operator new(const std::size_t size, Executor &, CompletionToken & token)
|
||||
{
|
||||
// gcc: 120 8 16
|
||||
// clang: 96 8 16
|
||||
return allocate_coroutine(size, asio::get_associated_allocator(token));
|
||||
}
|
||||
|
||||
void operator delete(void * raw, const std::size_t size)
|
||||
{
|
||||
deallocate_coroutine<Allocator>(raw, size);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<>
|
||||
struct partial_promise_base<std::allocator<void>> {};
|
||||
|
||||
|
||||
template<> struct partial_promise_base<void> {};
|
||||
template<typename T> struct partial_promise_base<std::allocator<T>> {};
|
||||
|
||||
// alloc options are two: allocator or aligned storage
|
||||
template<typename Allocator = void>
|
||||
struct partial_promise : partial_promise_base<Allocator>
|
||||
{
|
||||
auto initial_suspend() noexcept
|
||||
{
|
||||
return std::suspend_always();
|
||||
}
|
||||
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
return std::suspend_never();
|
||||
}
|
||||
|
||||
void return_void() {}
|
||||
};
|
||||
|
||||
template<typename Allocator = void>
|
||||
struct post_coroutine_promise : partial_promise<Allocator>
|
||||
{
|
||||
template<typename CompletionToken>
|
||||
auto yield_value(CompletionToken cpl)
|
||||
{
|
||||
struct awaitable_t
|
||||
{
|
||||
CompletionToken cpl;
|
||||
constexpr bool await_ready() noexcept { return false; }
|
||||
BOOST_NOINLINE
|
||||
auto await_suspend(std::coroutine_handle<void> h) noexcept
|
||||
{
|
||||
auto c = std::move(cpl);
|
||||
if (this_thread::has_executor())
|
||||
detail::self_destroy(h, asio::get_associated_executor(c, this_thread::get_executor()));
|
||||
else
|
||||
detail::self_destroy(h, asio::get_associated_executor(c));
|
||||
|
||||
asio::post(std::move(c));
|
||||
}
|
||||
|
||||
constexpr void await_resume() noexcept {}
|
||||
};
|
||||
return awaitable_t{std::move(cpl)};
|
||||
}
|
||||
|
||||
std::coroutine_handle<post_coroutine_promise<Allocator>> get_return_object()
|
||||
{
|
||||
return std::coroutine_handle<post_coroutine_promise<Allocator>>::from_promise(*this);
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
detail::self_destroy(std::coroutine_handle<post_coroutine_promise<Allocator>>::from_promise(*this));
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <typename T, typename ... Args>
|
||||
struct coroutine_traits<coroutine_handle<boost::cobalt::detail::post_coroutine_promise<T>>, Args...>
|
||||
{
|
||||
using promise_type = boost::cobalt::detail::post_coroutine_promise<T>;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
namespace boost::cobalt::detail
|
||||
{
|
||||
|
||||
|
||||
template <typename CompletionToken>
|
||||
auto post_coroutine(CompletionToken token)
|
||||
-> std::coroutine_handle<post_coroutine_promise<asio::associated_allocator_t<CompletionToken>>>
|
||||
{
|
||||
co_yield std::move(token);
|
||||
}
|
||||
|
||||
template <asio::execution::executor Executor, typename CompletionToken>
|
||||
auto post_coroutine(Executor exec, CompletionToken token)
|
||||
-> std::coroutine_handle<post_coroutine_promise<asio::associated_allocator_t<CompletionToken>>>
|
||||
{
|
||||
co_yield asio::bind_executor(exec, std::move(token));
|
||||
}
|
||||
|
||||
template <with_get_executor Context, typename CompletionToken>
|
||||
auto post_coroutine(Context &ctx, CompletionToken token)
|
||||
-> std::coroutine_handle<post_coroutine_promise<asio::associated_allocator_t<CompletionToken>>>
|
||||
{
|
||||
co_yield asio::bind_executor(ctx.get_executor(), std::move(token));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BOOST_COBALT_WRAPPER_HPP
|
||||
Reference in New Issue
Block a user