Added thirdparty: boost library

This commit is contained in:
Viacheslav Demydiuk
2024-01-06 19:55:56 +02:00
parent bf49f439e1
commit bccd1e7051
15683 changed files with 3239840 additions and 0 deletions
+82
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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] = &in;
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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