diff --git a/include/boost/capy/asio/detail/asio_context_service.hpp b/include/boost/capy/asio/detail/asio_context_service.hpp new file mode 100644 index 000000000..f1ac3e8c1 --- /dev/null +++ b/include/boost/capy/asio/detail/asio_context_service.hpp @@ -0,0 +1,37 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_DETAIL_ASIO_CONTEXT_SERVICE +#define BOOST_CAPY_ASIO_DETAIL_ASIO_CONTEXT_SERVICE + +#include + +namespace boost::capy::detail +{ + +template +struct asio_context_service + : Context::service + , capy::execution_context +{ + static Context::id id; + + asio_context_service(Context & ctx) + : Context::service(ctx) {} + void shutdown() override {capy::execution_context::shutdown();} +}; + + +// asio_context_service is templated for this id. +template +Context::id asio_context_service::id; + +} + +#endif diff --git a/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp new file mode 100644 index 000000000..d2da7ce97 --- /dev/null +++ b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp @@ -0,0 +1,49 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + + +#ifndef BOOST_CAPY_ASIO_DETAIL_ASIO_COROUTINE_UNIQUE_HANDLE +#define BOOST_CAPY_ASIO_DETAIL_ASIO_COROUTINE_UNIQUE_HANDLE + +#include +#include + +namespace boost::capy::detail +{ + +struct asio_coroutine_unique_handle +{ + struct deleter + { + deleter() = default; + void operator()(void * h) const + { + std::coroutine_handle::from_address(h).destroy(); + } + }; + std::unique_ptr handle; + + asio_coroutine_unique_handle( + std::coroutine_handle h) : handle(h.address()) {} + + asio_coroutine_unique_handle( + asio_coroutine_unique_handle && + ) noexcept = default; + + void operator()() + { + std::coroutine_handle::from_address( + handle.release() + ).resume(); + } +}; + +} + +#endif diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp new file mode 100644 index 000000000..c53c2f8ba --- /dev/null +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -0,0 +1,134 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER +#define BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace boost::capy::detail +{ + + +struct asio_immediate_executor_helper +{ + enum completed_immediately_t + { + no, maybe, yes, initiating + }; + + asio_executor_adapter exec; + completed_immediately_t * completed_immediately = nullptr; + + template + void execute(Fn && fn) const + { + // only allow it when we're still initializing + if (completed_immediately && + ((*completed_immediately == initiating) + || (*completed_immediately == 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 = maybe; + fn(); + + if (*completed_immediately != yes) + *completed_immediately = initiating; + } + else + { + boost::asio::post(exec, std::forward(fn)); + } + } + + friend bool operator==(const asio_immediate_executor_helper& lhs, + const asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec == rhs.exec; + } + + friend bool operator!=(const asio_immediate_executor_helper& lhs, + const asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec != rhs.exec; + } + + asio_immediate_executor_helper(const asio_immediate_executor_helper & rhs) noexcept = default; + asio_immediate_executor_helper(executor_ref inner, completed_immediately_t * completed_immediately) + : exec(std::move(inner)), completed_immediately(completed_immediately) + { + } +}; + + +template +struct asio_coroutine_completion_handler +{ + struct deleter + { + deleter() = default; + void operator()(void * h) const + { + std::coroutine_handle::from_address(h).destroy(); + } + }; + asio_coroutine_unique_handle handle; + std::optional> & result; + capy::io_env * env; + boost::asio::cancellation_slot slot; + asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; + + using allocator_type = std::pmr::polymorphic_allocator; + allocator_type get_allocator() const {return env->frame_allocator;} + + using executor_type = asio_executor_adapter; + executor_type get_executor() const {return env->executor;} + + using cancellation_slot_type = boost::asio::cancellation_slot; + cancellation_slot_type get_cancellation_slot() const {return slot;} + + using immediate_executor_type = asio_immediate_executor_helper; + immediate_executor_type get_immediate_executor() const + { + return immediate_executor_type{env->executor, completed_immediately }; + }; + + asio_coroutine_completion_handler( + std::coroutine_handle h, + std::optional> & result, + capy::io_env * env, + boost::asio::cancellation_slot slot = {}, + asio_immediate_executor_helper::completed_immediately_t * ci = nullptr) + : handle(h), result(result), env(env), slot(slot), completed_immediately(ci) {} + + asio_coroutine_completion_handler( + asio_coroutine_completion_handler && + ) noexcept = default; + + void operator()(Args ... args) + { + result.emplace(std::forward(args)...); + std::move(handle)(); + } +}; + +} + +#endif //BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER diff --git a/include/boost/capy/asio/detail/standalone_completion_handler.hpp b/include/boost/capy/asio/detail/standalone_completion_handler.hpp new file mode 100644 index 000000000..1f0055179 --- /dev/null +++ b/include/boost/capy/asio/detail/standalone_completion_handler.hpp @@ -0,0 +1,134 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_DETAIL_STANDALONE_COMPLETION_HANDLER +#define BOOST_CAPY_ASIO_DETAIL_STANDALONE_COMPLETION_HANDLER + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace boost::capy::detail +{ + + +struct standalone_asio_immediate_executor_helper +{ + enum completed_immediately_t + { + no, maybe, yes, initiating + }; + + standalone_asio_executor_adapter exec; + completed_immediately_t * completed_immediately = nullptr; + + template + void execute(Fn && fn) const + { + // only allow it when we're still initializing + if (completed_immediately && + ((*completed_immediately == initiating) + || (*completed_immediately == 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 = maybe; + fn(); + + if (*completed_immediately != yes) + *completed_immediately = initiating; + } + else + { + ::asio::post(exec, std::forward(fn)); + } + } + + friend bool operator==(const standalone_asio_immediate_executor_helper& lhs, + const standalone_asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec == rhs.exec; + } + + friend bool operator!=(const standalone_asio_immediate_executor_helper& lhs, + const standalone_asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec == rhs.exec; + } + + standalone_asio_immediate_executor_helper(const standalone_asio_immediate_executor_helper & rhs) noexcept = default; + standalone_asio_immediate_executor_helper(executor_ref inner, completed_immediately_t * completed_immediately) + : exec(std::move(inner)), completed_immediately(completed_immediately) + { + } +}; + + +template +struct standalone_asio_coroutine_completion_handler +{ + struct deleter + { + deleter() = default; + void operator()(void * h) const + { + std::coroutine_handle::from_address(h).destroy(); + } + }; + asio_coroutine_unique_handle handle; + std::optional> & result; + capy::io_env * env; + ::asio::cancellation_slot slot; + standalone_asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; + + using allocator_type = std::pmr::polymorphic_allocator; + allocator_type get_allocator() const {return env->frame_allocator;} + + using executor_type = standalone_asio_executor_adapter; + executor_type get_executor() const {return env->executor;} + + using cancellation_slot_type = ::asio::cancellation_slot; + cancellation_slot_type get_cancellation_slot() const {return slot;} + + using immediate_executor_type = standalone_asio_immediate_executor_helper; + immediate_executor_type get_immediate_executor() const + { + return immediate_executor_type{env->executor, completed_immediately }; + }; + + standalone_asio_coroutine_completion_handler( + std::coroutine_handle h, + std::optional> & result, + capy::io_env * env, + ::asio::cancellation_slot slot = {}, + standalone_asio_immediate_executor_helper::completed_immediately_t * ci = nullptr) + : handle(h), result(result), env(env), slot(slot), completed_immediately(ci) {} + + standalone_asio_coroutine_completion_handler( + standalone_asio_coroutine_completion_handler && + ) noexcept = default; + + void operator()(Args ... args) + { + result.emplace(std::forward(args)...); + std::move(handle)(); + } +}; + +} + +#endif //BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER diff --git a/include/boost/capy/asio/executor_adapter.hpp b/include/boost/capy/asio/executor_adapter.hpp new file mode 100644 index 000000000..a68e9332d --- /dev/null +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -0,0 +1,298 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail +{ + +struct asio_adapter_context_service + : execution_context::service, + // shutdown is protected + boost::asio::execution_context +{ + asio_adapter_context_service(boost::capy::execution_context & ctx) {} + void shutdown() override {boost::asio::execution_context::shutdown();} +}; + +} + + +template, + typename Blocking = boost::asio::execution::blocking_t::possibly_t, + typename Outstanding = boost::asio::execution::outstanding_work_t::untracked_t> +struct asio_executor_adapter +{ + template + asio_executor_adapter(const asio_executor_adapter & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_), allocator_(rhs.allocator_) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + template + asio_executor_adapter(asio_executor_adapter && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)), allocator_(std::move(rhs.allocator_)) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + asio_executor_adapter(Executor executor, const Allocator & alloc) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)), allocator_(alloc) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + asio_executor_adapter(Executor executor) noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)), allocator_(executor_.context().get_frame_allocator()) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + ~asio_executor_adapter() + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_finished(); + } + + template + asio_executor_adapter & operator=(const asio_executor_adapter & rhs) + { + + if constexpr (Outstanding_() == boost::asio::execution::outstanding_work.tracked) + if (rhs.executor_ != executor_) + { + rhs.executor_.on_work_started(); + executor_.on_work_finished(); + } + + executor_ = rhs.executor_; + allocator_ = rhs.allocator_; + } + + bool operator==(const asio_executor_adapter & rhs) const noexcept + { + return executor_ == rhs.executor_ + && allocator_ == rhs.allocator_; + } + bool operator!=(const asio_executor_adapter & rhs) const noexcept + { + return executor_ != rhs.executor_ + && allocator_ != rhs.allocator_; + } + + boost::asio::execution_context& + query(boost::asio::execution::context_t) const noexcept + { + return context(); + } + + constexpr boost::asio::execution::blocking_t + query(boost::asio::execution::blocking_t) const noexcept + { + return Blocking(); + } + + constexpr asio_executor_adapter + require(boost::asio::execution::blocking_t::possibly_t) const + { + return *this; + } + + constexpr asio_executor_adapter + require(boost::asio::execution::blocking_t::never_t) const + { + return *this; + } + + constexpr asio_executor_adapter + require(boost::asio::execution::blocking_t::always_t) const + { + return *this; + } + + static constexpr boost::asio::execution::outstanding_work_t query( + boost::asio::execution::outstanding_work_t) noexcept + { + return Outstanding(); + } + + constexpr asio_executor_adapter + require(boost::asio::execution::outstanding_work_t::tracked_t) const + { + return *this; + } + + constexpr asio_executor_adapter + require(boost::asio::execution::outstanding_work_t::untracked_t) const + { + return *this; + } + + + template + constexpr Allocator query( + boost::asio::execution::allocator_t) const noexcept + { + return allocator_; + } + template + constexpr asio_executor_adapter + require(boost::asio::execution::allocator_t a) const + { + return asio_executor_adapter( + executor_, a.value() + ); + } + + boost::asio::execution_context & context() const noexcept + { + return executor_.context().template use_service(); + } + + template + void execute(Function&& f) const + { + constexpr static boost::asio::execution::blocking_t b; + if constexpr (Blocking() == b.never) + executor_.post(make_handle_(std::forward(f)).cont); + else if constexpr(Blocking() == b.possibly) + executor_.dispatch(make_handle_(std::forward(f)).cont).resume(); + else if constexpr(Blocking() == b.always) + std::forward(f)(); + } + + + private: + + struct handler_promise_base_empty_ {}; + struct handler_promise_base_ + { + using alloc_t = std::allocator_traits::template rebind_alloc; + template + void * operator new(std::size_t n, const asio_executor_adapter & adapter, Func &) + { + alloc_t alloc(adapter.allocator_); + std::size_t m = n; + if (n % alignof(alloc_t) > 0) + m += alignof(alloc_t) - (n % alignof(alloc_t)); + + char * mem = alloc.allocate(m + sizeof(alloc)); + + new (mem + m) alloc_t(std::move(alloc)); + return mem; + } + + void operator delete(void * p, std::size_t n) + { + std::size_t m = n; + if (n % alignof(alloc_t) > 0) + m += alignof(alloc_t) - (n % alignof(alloc_t)); + + auto * a = reinterpret_cast(static_cast(p) + m); + + alloc_t alloc(std::move(*a)); + a->~alloc_t(); + + alloc.deallocate(static_cast(p), n); + } + + }; + + struct handler_promise_ : std::conditional_t< + std::same_as>, + handler_promise_base_empty_, + handler_promise_base_> + { + std::suspend_always initial_suspend() const noexcept {return {};} + std::suspend_never final_suspend() const noexcept {return {};} + + template + auto yield_value(Function & func) + { + struct yielder + { + Function func; + + bool await_ready() const {return false;} + void await_suspend(std::coroutine_handle<> h) + { + auto f = std::move(func); + h.destroy(); + std::move(f)(); + } + void await_resume() {} + }; + + return yielder{std::move(func)}; + } + + continuation cont; + + void unhandled_exception() { throw; } + continuation & get_return_object() + { + cont.h = std::coroutine_handle::from_promise(*this); + cont.next = nullptr; + return cont; + } + }; + + struct helper_ + { + capy::continuation &cont; + helper_(continuation & cont) noexcept : cont(cont) {} + using promise_type = handler_promise_; + }; + + template + helper_ make_handle_(Function func) const + { + co_yield func; + } + + template + friend struct asio_executor_adapter; + Executor executor_; + [[no_unique_address]] Allocator allocator_; + +}; + + + + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/include/boost/capy/asio/executor_from_asio.hpp b/include/boost/capy/asio/executor_from_asio.hpp new file mode 100644 index 000000000..e7f7a88f3 --- /dev/null +++ b/include/boost/capy/asio/executor_from_asio.hpp @@ -0,0 +1,227 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_EXECUTOR_FROM_ASIO_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_FROM_ASIO_HPP + +#include +#include +#include + +#include +#include +#include + +namespace boost { +namespace capy { + + + +template + requires requires (Executor exec) + { + { + boost::asio::prefer( + std::move(exec), + boost::asio::execution::outstanding_work.tracked + ) + } -> std::convertible_to; + { + boost::asio::prefer( + std::move(exec), + boost::asio::execution::outstanding_work.untracked + ) + } -> std::convertible_to; + } +struct executor_from_asio_properties +{ + executor_from_asio_properties(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_asio_properties(executor_from_asio_properties && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_asio_properties(const executor_from_asio_properties & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + auto & ec = boost::asio::query(executor_, boost::asio::execution::context); + return boost::asio::use_service>(ec); + } + + void on_work_started() const noexcept + { + + using boost::asio::execution::outstanding_work; + if (boost::asio::query(executor_, outstanding_work) == outstanding_work.untracked) + executor_ = boost::asio::prefer( + std::move(executor_), outstanding_work.tracked); + } + + void on_work_finished() const noexcept + { + using boost::asio::execution::outstanding_work; + if (boost::asio::query(executor_, outstanding_work) == outstanding_work.tracked) + executor_ = boost::asio::prefer( + std::move(executor_), outstanding_work.untracked); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + boost::asio::dispatch( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + boost::asio::post( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + } + + bool operator==(const executor_from_asio_properties & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_asio_properties & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + mutable Executor executor_; +}; + + +template + requires requires (Executor exec) + { + exec.on_work_started(); + exec.on_work_finished(); + } +struct executor_from_asio_net_ts +{ + executor_from_asio_net_ts(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_asio_net_ts(executor_from_asio_net_ts && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_asio_net_ts(const executor_from_asio_net_ts & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + return boost::asio::use_service> + ( + executor_.context() + ); + } + + void on_work_started() const noexcept + { + executor_.on_work_started(); + } + + void on_work_finished() const noexcept + { + executor_.on_work_finished(); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + executor_.dispatch( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + executor_.post( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + } + + bool operator==(const executor_from_asio_net_ts & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_asio_net_ts & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + Executor executor_; +}; + + +namespace detail +{ + + +struct executor_from_asio_net_ts_helper +{ + template + using impl = executor_from_asio_net_ts; +}; + +struct executor_from_asio_properties_helper +{ + template + using impl = executor_from_asio_properties; +}; + +template +using executor_from_asio_helper = + std::conditional_t< + requires (Executor exec) {{exec.on_work_started()};}, + executor_from_asio_net_ts_helper, + executor_from_asio_properties_helper> + ::template impl; + +} + +template +struct executor_from_asio : detail::executor_from_asio_helper +{ + using detail::executor_from_asio_helper::executor_from_asio_helper; +}; + +template +executor_from_asio(Executor) -> executor_from_asio; + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/include/boost/capy/asio/executor_from_standalone_asio.hpp b/include/boost/capy/asio/executor_from_standalone_asio.hpp new file mode 100644 index 000000000..b10adfb1e --- /dev/null +++ b/include/boost/capy/asio/executor_from_standalone_asio.hpp @@ -0,0 +1,228 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_EXECUTOR_FROM_STANDALONE_ASIO_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_FROM_STANDALONE_ASIO_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace boost { +namespace capy { + + + +template + requires requires (Executor exec) + { + { + ::asio::prefer( + std::move(exec), + ::asio::execution::outstanding_work.tracked + ) + } -> std::convertible_to; + { + ::asio::prefer( + std::move(exec), + ::asio::execution::outstanding_work.untracked + ) + } -> std::convertible_to; + } +struct executor_from_standalone_asio_properties +{ + executor_from_standalone_asio_properties(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_standalone_asio_properties(executor_from_standalone_asio_properties && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_standalone_asio_properties(const executor_from_standalone_asio_properties & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + auto & ec = ::asio::query(executor_, ::asio::execution::context); + return ::asio::use_service>(ec); + } + + void on_work_started() const noexcept + { + + using ::asio::execution::outstanding_work; + if (::asio::query(executor_, outstanding_work) == outstanding_work.untracked) + executor_ = ::asio::prefer( + std::move(executor_), outstanding_work.tracked); + } + + void on_work_finished() const noexcept + { + using ::asio::execution::outstanding_work; + if (::asio::query(executor_, outstanding_work) == outstanding_work.tracked) + executor_ = ::asio::prefer( + std::move(executor_), outstanding_work.untracked); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + ::asio::dispatch( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + ::asio::post( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + } + + bool operator==(const executor_from_standalone_asio_properties & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_standalone_asio_properties & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + mutable Executor executor_; +}; + + +template + requires requires (Executor exec) + { + exec.on_work_started(); + exec.on_work_finished(); + } +struct executor_from_standalone_asio_net_ts +{ + executor_from_standalone_asio_net_ts(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_standalone_asio_net_ts(executor_from_standalone_asio_net_ts && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_standalone_asio_net_ts(const executor_from_standalone_asio_net_ts & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + return ::asio::use_service> + ( + executor_.context() + ); + } + + void on_work_started() const noexcept + { + executor_.on_work_started(); + } + + void on_work_finished() const noexcept + { + executor_.on_work_finished(); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + executor_.dispatch( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + executor_.post( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + } + + bool operator==(const executor_from_standalone_asio_net_ts & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_standalone_asio_net_ts & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + Executor executor_; +}; + + +namespace detail +{ + + +struct executor_from_standalone_asio_net_ts_helper +{ + template + using impl = executor_from_standalone_asio_net_ts; +}; + +struct executor_from_standalone_asio_properties_helper +{ + template + using impl = executor_from_standalone_asio_properties; +}; + +template +using executor_from_standalone_asio_helper = + std::conditional_t< + requires (Executor exec) {{exec.on_work_started()};}, + executor_from_standalone_asio_net_ts_helper, + executor_from_standalone_asio_properties_helper> + ::template impl; + +} + +template +struct executor_from_standalone_asio : detail::executor_from_standalone_asio_helper +{ + using detail::executor_from_standalone_asio_helper::executor_from_standalone_asio_helper; +}; + +template +executor_from_standalone_asio(Executor) -> executor_from_standalone_asio; + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/include/boost/capy/asio/standalone_executor_adapter.hpp b/include/boost/capy/asio/standalone_executor_adapter.hpp new file mode 100644 index 000000000..978e39491 --- /dev/null +++ b/include/boost/capy/asio/standalone_executor_adapter.hpp @@ -0,0 +1,295 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_STANDALONE_EXECUTOR_ADAPTER_HPP +#define BOOST_CAPY_ASIO_STANDALONE_EXECUTOR_ADAPTER_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail +{ + +struct standalone_asio_adapter_context_service + : execution_context::service, + // shutdown is protected + ::asio::execution_context +{ + standalone_asio_adapter_context_service(boost::capy::execution_context & ctx) {} + void shutdown() override {::asio::execution_context::shutdown();} +}; + +} + +template, + typename Blocking = ::asio::execution::blocking_t::possibly_t, + typename Outstanding = ::asio::execution::outstanding_work_t::untracked_t> +struct standalone_asio_executor_adapter +{ + template + standalone_asio_executor_adapter(const standalone_asio_executor_adapter & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_), allocator_(rhs.allocator_) + { + if constexpr(Outstanding() == ::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + template + standalone_asio_executor_adapter(standalone_asio_executor_adapter && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)), allocator_(std::move(rhs.allocator_)) + { + if constexpr(Outstanding() == ::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + standalone_asio_executor_adapter(Executor executor, const Allocator & alloc) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)), allocator_(alloc) + { + if constexpr(Outstanding() == ::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + standalone_asio_executor_adapter(Executor executor) noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)), allocator_(executor_.context().get_frame_allocator()) + { + if constexpr(Outstanding() == ::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + ~standalone_asio_executor_adapter() + { + if constexpr(Outstanding() == ::asio::execution::outstanding_work.tracked) + executor_.on_work_finished(); + } + + template + standalone_asio_executor_adapter & operator=(const standalone_asio_executor_adapter & rhs) + { + + if constexpr (Outstanding_() == ::asio::execution::outstanding_work.tracked) + if (rhs.executor_ != executor_) + { + rhs.executor_.on_work_started(); + executor_.on_work_finished(); + } + + executor_ = rhs.executor_; + allocator_ = rhs.allocator_; + } + + bool operator==(const standalone_asio_executor_adapter & rhs) const noexcept + { + return executor_ == rhs.executor_ + && allocator_ == rhs.allocator_; + } + bool operator!=(const standalone_asio_executor_adapter & rhs) const noexcept + { + return executor_ != rhs.executor_ + && allocator_ != rhs.allocator_; + } + + ::asio::execution_context& + query(::asio::execution::context_t) const noexcept + { + return context(); + } + + constexpr ::asio::execution::blocking_t + query(::asio::execution::blocking_t) const noexcept + { + return Blocking(); + } + + constexpr standalone_asio_executor_adapter + require(::asio::execution::blocking_t::possibly_t) const + { + return *this; + } + + constexpr standalone_asio_executor_adapter + require(::asio::execution::blocking_t::never_t) const + { + return *this; + } + + constexpr standalone_asio_executor_adapter + require(::asio::execution::blocking_t::always_t) const + { + return *this; + } + + static constexpr ::asio::execution::outstanding_work_t query( + ::asio::execution::outstanding_work_t) noexcept + { + return Outstanding(); + } + + constexpr standalone_asio_executor_adapter + require(::asio::execution::outstanding_work_t::tracked_t) const + { + return *this; + } + + constexpr standalone_asio_executor_adapter + require(::asio::execution::outstanding_work_t::untracked_t) const + { + return *this; + } + + + template + constexpr Allocator query( + ::asio::execution::allocator_t) const noexcept + { + return allocator_; + } + template + constexpr standalone_asio_executor_adapter + require(::asio::execution::allocator_t a) const + { + return standalone_asio_executor_adapter( + executor_, a.value() + ); + } + + ::asio::execution_context & context() const noexcept + { + return executor_.context().template use_service(); + } + + template + void execute(Function&& f) const + { + constexpr static ::asio::execution::blocking_t b; + if constexpr (Blocking() == b.never) + executor_.post(make_handle_(std::forward(f)).cont); + else if constexpr(Blocking() == b.possibly) + executor_.dispatch(make_handle_(std::forward(f)).cont).resume(); + else if constexpr(Blocking() == b.always) + std::forward(f)(); + } + + private: + + struct handler_promise_base_empty_ {}; + struct handler_promise_base_ + { + using alloc_t = std::allocator_traits::template rebind_alloc; + template + void * operator new(std::size_t n, const standalone_asio_executor_adapter & adapter, Func &) + { + alloc_t alloc(adapter.allocator_); + std::size_t m = n; + if (n % alignof(alloc_t) > 0) + m += alignof(alloc_t) - (n % alignof(alloc_t)); + + char * mem = alloc.allocate(m + sizeof(alloc)); + + new (mem + m) alloc_t(std::move(alloc)); + return mem; + } + + void operator delete(void * p, std::size_t n) + { + std::size_t m = n; + if (n % alignof(alloc_t) > 0) + m += alignof(alloc_t) - (n % alignof(alloc_t)); + + auto * a = reinterpret_cast(static_cast(p) + m); + + alloc_t alloc(std::move(*a)); + a->~alloc_t(); + + alloc.deallocate(static_cast(p), n); + } + + }; + + struct handler_promise_ : std::conditional_t< + std::same_as>, + handler_promise_base_empty_, + handler_promise_base_> + { + std::suspend_always initial_suspend() const noexcept {return {};} + std::suspend_never final_suspend() const noexcept {return {};} + + template + auto yield_value(Function & func) + { + struct yielder + { + Function func; + + bool await_ready() const {return false;} + void await_suspend(std::coroutine_handle<> h) + { + auto f = std::move(func); + h.destroy(); + std::move(f)(); + } + void await_resume() {} + }; + + return yielder{std::move(func)}; + } + + continuation cont; + + void unhandled_exception() { throw; } + continuation & get_return_object() + { + cont.h = std::coroutine_handle::from_promise(*this); + cont.next = nullptr; + return cont; + } + }; + + struct helper_ + { + capy::continuation &cont; + helper_(continuation & cont) noexcept : cont(cont) {} + using promise_type = handler_promise_; + }; + + template + helper_ make_handle_(Function func) const + { + co_yield func; + } + + template + friend struct standalone_asio_executor_adapter; + Executor executor_; + [[no_unique_address]] Allocator allocator_; + +}; + + + + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp new file mode 100644 index 000000000..82bd6d3bd --- /dev/null +++ b/test/unit/asio.cpp @@ -0,0 +1,143 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +#if __has_include() +#include + +#include +#include +#include +#include +#endif + +#if __has_include() +#include + +#include +#include +#include +#include + +#endif + + +#include +#include "test_helpers.hpp" +#include "test_suite.hpp" + +#include + +namespace boost { +namespace capy { + + +#if __has_include() + +struct asio_standalone_test +{ + void testExecutor() + { + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::standalone_asio_executor_adapter wrapped_te{exec}; + + bool ran = false; + ::asio::post(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 0); + BOOST_TEST(ran); + + + ran = false; + ::asio::dispatch(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 1); + BOOST_TEST(ran); + + BOOST_TEST(work_cnt == 0); + { + auto wk = ::asio::require(wrapped_te, ::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + + } + BOOST_TEST_EQ(work_cnt, 0); + ::asio::any_io_executor aio{wrapped_te}; + BOOST_TEST_EQ(work_cnt, 0); + aio = ::asio::prefer(aio, ::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + aio = nullptr; + BOOST_TEST_EQ(work_cnt, 0); + } + + void run() + { + testExecutor(); + } +}; + +#endif + +#if __has_include() + +struct boost_asio_test +{ + void testExecutor() + { + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::asio_executor_adapter wrapped_te{exec}; + + bool ran = false; + boost::asio::post(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 0); + BOOST_TEST(ran); + + + ran = false; + boost::asio::dispatch(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 1); + BOOST_TEST(ran); + + BOOST_TEST(work_cnt == 0); + { + auto wk = boost::asio::require(wrapped_te, boost::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + + } + BOOST_TEST_EQ(work_cnt, 0); + boost::asio::any_io_executor aio{wrapped_te}; + BOOST_TEST_EQ(work_cnt, 0); + aio = boost::asio::prefer(aio, boost::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + aio = nullptr; + BOOST_TEST_EQ(work_cnt, 0); + } + + void run() + { + testExecutor(); + } +}; + +#endif + +#if __has_include() +TEST_SUITE( + asio_standalone_test, + "boost.capy.asio.standalone"); +#endif + +#if __has_include() +TEST_SUITE( + boost_asio_test, + "boost.capy.asio.boost"); +#endif + +} // namespace capy +} // namespace boost diff --git a/test/unit/test_helpers.hpp b/test/unit/test_helpers.hpp index ef070f9ac..4911cae1b 100644 --- a/test/unit/test_helpers.hpp +++ b/test/unit/test_helpers.hpp @@ -71,8 +71,9 @@ struct test_executor { int id_ = 0; int* dispatch_count_ = nullptr; + int* work_count_ = nullptr; test_io_context* ctx_ = nullptr; - + test_executor() = default; explicit @@ -87,12 +88,26 @@ struct test_executor { } + explicit + test_executor(int& count, int & work) noexcept + : dispatch_count_(&count) + , work_count_(&work) + { + } + test_executor(int id, int& count) noexcept : id_(id) , dispatch_count_(&count) { } + test_executor(int id, int& count, int& work) noexcept + : id_(id) + , dispatch_count_(&count) + , work_count_(&work) + { + } + bool operator==(test_executor const& other) const noexcept { @@ -104,8 +119,8 @@ struct test_executor test_io_context& context() const noexcept; - void on_work_started() const noexcept {} - void on_work_finished() const noexcept {} + void on_work_started() const noexcept {if (work_count_) (*work_count_)++;} + void on_work_finished() const noexcept {if (work_count_) (*work_count_)--;} std::coroutine_handle<> dispatch(continuation& c) const