From 93351c4b2f7d541f8141e3ad4cfa53aba18fc056 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 30 Mar 2026 21:43:11 +0800 Subject: [PATCH 01/29] test_executor has a work count --- test/unit/test_helpers.hpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/unit/test_helpers.hpp b/test/unit/test_helpers.hpp index 08f421f5..1c3d6a38 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 From aca7ecc5155764306db723e2cf715c91d89dca47 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 30 Mar 2026 21:44:06 +0800 Subject: [PATCH 02/29] capy executors can be used by asio --- include/boost/capy/asio/executor_adapter.hpp | 298 ++++++++++++++++++ .../asio/standalone_adapter_standalone.hpp | 295 +++++++++++++++++ test/unit/asio.cpp | 143 +++++++++ 3 files changed, 736 insertions(+) create mode 100644 include/boost/capy/asio/executor_adapter.hpp create mode 100644 include/boost/capy/asio/standalone_adapter_standalone.hpp create mode 100644 test/unit/asio.cpp diff --git a/include/boost/capy/asio/executor_adapter.hpp b/include/boost/capy/asio/executor_adapter.hpp new file mode 100644 index 00000000..a68e9332 --- /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/standalone_adapter_standalone.hpp b/include/boost/capy/asio/standalone_adapter_standalone.hpp new file mode 100644 index 00000000..5c437ebc --- /dev/null +++ b/include/boost/capy/asio/standalone_adapter_standalone.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_EXECUTOR_STANDALONE_ADAPTER_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_STANDALONE_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 00000000..82bd6d3b --- /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 From 879a75238e98d4ba7c0f6cceabbeb06ffb25f3bb Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Tue, 31 Mar 2026 07:50:08 +0800 Subject: [PATCH 03/29] Capy can run on asio executors --- .../capy/asio/detail/asio_context_service.hpp | 37 +++ .../detail/asio_coroutine_unique_handle.hpp | 49 ++++ .../capy/asio/detail/completion_handler.hpp | 134 ++++++++++ .../detail/standalone_completion_handler.hpp | 134 ++++++++++ .../boost/capy/asio/executor_from_asio.hpp | 227 +++++++++++++++++ .../asio/executor_from_standalone_asio.hpp | 228 ++++++++++++++++++ ...ne.hpp => standalone_executor_adapter.hpp} | 4 +- 7 files changed, 811 insertions(+), 2 deletions(-) create mode 100644 include/boost/capy/asio/detail/asio_context_service.hpp create mode 100644 include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp create mode 100644 include/boost/capy/asio/detail/completion_handler.hpp create mode 100644 include/boost/capy/asio/detail/standalone_completion_handler.hpp create mode 100644 include/boost/capy/asio/executor_from_asio.hpp create mode 100644 include/boost/capy/asio/executor_from_standalone_asio.hpp rename include/boost/capy/asio/{standalone_adapter_standalone.hpp => standalone_executor_adapter.hpp} (98%) 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 00000000..f1ac3e8c --- /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 00000000..d2da7ce9 --- /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 00000000..c53c2f8b --- /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 00000000..1f005517 --- /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_from_asio.hpp b/include/boost/capy/asio/executor_from_asio.hpp new file mode 100644 index 00000000..e7f7a88f --- /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 00000000..b10adfb1 --- /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_adapter_standalone.hpp b/include/boost/capy/asio/standalone_executor_adapter.hpp similarity index 98% rename from include/boost/capy/asio/standalone_adapter_standalone.hpp rename to include/boost/capy/asio/standalone_executor_adapter.hpp index 5c437ebc..978e3949 100644 --- a/include/boost/capy/asio/standalone_adapter_standalone.hpp +++ b/include/boost/capy/asio/standalone_executor_adapter.hpp @@ -7,8 +7,8 @@ // Official repository: https://github.com/cppalliance/capy // -#ifndef BOOST_CAPY_ASIO_EXECUTOR_STANDALONE_ADAPTER_HPP -#define BOOST_CAPY_ASIO_EXECUTOR_STANDALONE_ADAPTER_HPP +#ifndef BOOST_CAPY_ASIO_STANDALONE_EXECUTOR_ADAPTER_HPP +#define BOOST_CAPY_ASIO_STANDALONE_EXECUTOR_ADAPTER_HPP #include #include From 2a93ff9f04b8c5976e9798c911d3133820bbbb03 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 2 Apr 2026 07:09:21 +0800 Subject: [PATCH 04/29] Asio ops can be awaited using 'as_io_awaitable' --- include/boost/capy/asio/as_io_awaitable.hpp | 86 +++++++++++++++++ .../capy/asio/detail/as_io_awaitable.hpp | 76 +++++++++++++++ .../capy/asio/detail/completion_handler.hpp | 4 +- .../detail/standalone_completion_handler.hpp | 4 +- .../capy/asio/standalone_as_io_awaitable.hpp | 92 +++++++++++++++++++ 5 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 include/boost/capy/asio/as_io_awaitable.hpp create mode 100644 include/boost/capy/asio/detail/as_io_awaitable.hpp create mode 100644 include/boost/capy/asio/standalone_as_io_awaitable.hpp diff --git a/include/boost/capy/asio/as_io_awaitable.hpp b/include/boost/capy/asio/as_io_awaitable.hpp new file mode 100644 index 00000000..dd83ab68 --- /dev/null +++ b/include/boost/capy/asio/as_io_awaitable.hpp @@ -0,0 +1,86 @@ +// +// 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_AS_IO_AWAITABLE +#define BOOST_CAPY_ASIO_AS_IO_AWAITABLE + +#include +#include +#include + +#include + + +template +struct boost::asio::async_result +{ + template + struct awaitable_t + { + cancellation_signal signal; + capy::detail::asio_immediate_executor_helper::completed_immediately_t ci; + + struct cb + { + cancellation_signal &signal; + cb(cancellation_signal &signal) : signal(signal) {} + void operator()() {signal.emit(cancellation_type::terminal); } + }; + std::optional> stopper; + + bool await_ready() const {return false;} + + void await_suspend(std::coroutine_handle<> h, const capy::io_env * env) + { + stopper.emplace(env->stop_token, signal); + capy::detail::asio_coroutine_completion_handler ch( + h, result_, env, + signal.slot(), + &ci); + + std::apply( + [&](auto ... args) + { + std::move(init_)( + std::move(ch), + std::move(args)...); + }, + std::move(args_)); + + } + + std::tuple await_resume() {return std::move(*result_); } + + + awaitable_t(Initiation init, std::tuple args) + : init_(std::move(init)), args_(std::move(args)) {} + awaitable_t(awaitable_t && rhs) noexcept + : init_(std::move(rhs.init_)), args_(std::move(rhs.args_)), result_(std::move(rhs.result_)) {} + private: + Initiation init_; + std::tuple args_; + std::optional> result_; + + }; + + template + static auto initiate(Initiation&& initiation, + RawCompletionToken&&, Args&&... args) + { + return awaitable_t< + std::decay_t, + std::decay_t...>( + std::forward(initiation), + std::make_tuple(std::forward(args)...)); + } +}; + + +#endif + diff --git a/include/boost/capy/asio/detail/as_io_awaitable.hpp b/include/boost/capy/asio/detail/as_io_awaitable.hpp new file mode 100644 index 00000000..981f00e1 --- /dev/null +++ b/include/boost/capy/asio/detail/as_io_awaitable.hpp @@ -0,0 +1,76 @@ +// 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_AS_IO_AWAITABLE_HPP +#define BOOST_CAPY_ASIO_DETAIL_AS_IO_AWAITABLE_HPP + +#include +#include + +namespace boost::capy +{ + + +struct as_io_awaitable_t +{ + /// Default constructor. + constexpr as_io_awaitable_t() + { + } + + /// Adapts an executor to add the @c use_op_t completion token as the + /// default. + template + struct executor_with_default : InnerExecutor + { + /// Specify @c use_op_t as the default completion token type. + typedef as_io_awaitable_t default_completion_token_type; + + executor_with_default(const InnerExecutor& ex) noexcept + : InnerExecutor(ex) + { + } + + /// Construct the adapted executor from the inner executor type. + template + executor_with_default(const InnerExecutor1& ex, + typename std::enable_if< + std::conditional< + !std::is_same::value, + std::is_convertible, + std::false_type + >::type::value>::type = 0) noexcept + : InnerExecutor(ex) + { + } + }; + + /// Type alias to adapt an I/O object to use @c use_op_t as its + /// default completion token type. + template + using as_default_on_t = typename T::template rebind_executor< + executor_with_default >::other; + + /// Function helper to adapt an I/O object to use @c use_op_t as its + /// default completion token type. + template + static typename std::decay_t::template rebind_executor< + executor_with_default::executor_type> + >::other + as_default_on(T && object) + { + return typename std::decay_t::template rebind_executor< + executor_with_default::executor_type> + >::other(std::forward(object)); + } +}; +constexpr as_io_awaitable_t as_io_awaitable; + +} + +#endif diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index c53c2f8b..dbf415ec 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -91,7 +91,7 @@ struct asio_coroutine_completion_handler }; asio_coroutine_unique_handle handle; std::optional> & result; - capy::io_env * env; + const capy::io_env * env; boost::asio::cancellation_slot slot; asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; @@ -113,7 +113,7 @@ struct asio_coroutine_completion_handler asio_coroutine_completion_handler( std::coroutine_handle h, std::optional> & result, - capy::io_env * env, + const 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) {} diff --git a/include/boost/capy/asio/detail/standalone_completion_handler.hpp b/include/boost/capy/asio/detail/standalone_completion_handler.hpp index 1f005517..21c710cf 100644 --- a/include/boost/capy/asio/detail/standalone_completion_handler.hpp +++ b/include/boost/capy/asio/detail/standalone_completion_handler.hpp @@ -91,7 +91,7 @@ struct standalone_asio_coroutine_completion_handler }; asio_coroutine_unique_handle handle; std::optional> & result; - capy::io_env * env; + const capy::io_env * env; ::asio::cancellation_slot slot; standalone_asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; @@ -113,7 +113,7 @@ struct standalone_asio_coroutine_completion_handler standalone_asio_coroutine_completion_handler( std::coroutine_handle h, std::optional> & result, - capy::io_env * env, + const 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) {} diff --git a/include/boost/capy/asio/standalone_as_io_awaitable.hpp b/include/boost/capy/asio/standalone_as_io_awaitable.hpp new file mode 100644 index 00000000..4a65932a --- /dev/null +++ b/include/boost/capy/asio/standalone_as_io_awaitable.hpp @@ -0,0 +1,92 @@ +// +// 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_AS_IO_AWAITABLE +#define BOOST_CAPY_ASIO_STANDALONE_AS_IO_AWAITABLE + +#include +#include +#include + +#include + +template +struct asio::async_result +{ + template + struct awaitable_t + { + cancellation_signal signal; + boost::capy::detail::standalone_asio_immediate_executor_helper::completed_immediately_t ci; + + struct cb + { + cancellation_signal &signal; + cb(cancellation_signal &signal) : signal(signal) {} + void operator()() {signal.emit(cancellation_type::terminal); } + }; + std::optional> stopper; + + bool await_ready() const {return false;} + + bool await_suspend(std::coroutine_handle<> h, const boost::capy::io_env * env) + { + stopper.emplace(env->stop_token, signal); + boost::capy::detail::standalone_asio_coroutine_completion_handler ch( + h, result_, env, + signal.slot(), + &ci); + + using ci_t = boost::capy::detail::standalone_asio_immediate_executor_helper::completed_immediately_t; + ci = ci_t::initiating; + + + std::apply( + [&](auto ... args) + { + std::move(init_)( + std::move(ch), + std::move(args)...); + }, + std::move(args_)); + + if (ci == ci_t::initiating) + ci = ci_t::no; + return ci != ci_t::yes; + } + + std::tuple await_resume() {return std::move(*result_); } + + + awaitable_t(Initiation init, std::tuple args) + : init_(std::move(init)), args_(std::move(args)) {} + + awaitable_t(awaitable_t && rhs) noexcept + : init_(std::move(rhs.init_)), args_(std::move(rhs.args_)), result_(std::move(rhs.result_)) {} private: + Initiation init_; + std::tuple args_; + std::optional> result_; + + }; + + template + static auto initiate(Initiation&& initiation, + RawCompletionToken&& token, Args&&... args) + { + return awaitable_t< + std::decay_t, + std::decay_t...>( + std::forward(initiation), + std::make_tuple(std::forward(args)...)); + } +}; + + +#endif + From b86059e49df0b936f1435c6ec5f54f52a0621e5e Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 3 Apr 2026 07:35:59 +0800 Subject: [PATCH 05/29] IoRunnables can be spawned to asio --- .../capy/asio/detail/completion_handler.hpp | 4 +- .../capy/asio/detail/completion_traits.hpp | 72 +++++ include/boost/capy/asio/executor_adapter.hpp | 1 + include/boost/capy/asio/spawn.hpp | 267 ++++++++++++++++++ include/boost/capy/asio/standalone_spawn.hpp | 267 ++++++++++++++++++ test/unit/asio.cpp | 178 +++++++++++- 6 files changed, 785 insertions(+), 4 deletions(-) create mode 100644 include/boost/capy/asio/detail/completion_traits.hpp create mode 100644 include/boost/capy/asio/spawn.hpp create mode 100644 include/boost/capy/asio/standalone_spawn.hpp diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index dbf415ec..ebc779ae 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -7,8 +7,8 @@ // Official repository: https://github.com/cppalliance/capy // -#ifndef BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER -#define BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER +#ifndef BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER_HPP +#define BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER_HPP #include #include diff --git a/include/boost/capy/asio/detail/completion_traits.hpp b/include/boost/capy/asio/detail/completion_traits.hpp new file mode 100644 index 00000000..3510dc66 --- /dev/null +++ b/include/boost/capy/asio/detail/completion_traits.hpp @@ -0,0 +1,72 @@ +// +// 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_TRAITS_HPP +#define BOOST_CAPY_ASIO_DETAIL_COMPLETION_TRAITS_HPP + +#include + +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + + +template +struct completion_traits +{ + using signature_type = void(std::exception_ptr, ResultType); + using result_type = std::tuple; +}; + +template +struct completion_traits +{ + using signature_type = void(ResultType); + using result_type = std::tuple; +}; + + +template<> +struct completion_traits +{ + using signature_type = void(std::exception_ptr); + using result_type = std::tuple; +}; + +template<> +struct completion_traits +{ + using signature_type = void(); + using result_type = std::tuple<>; +}; + +template +using completion_signature_for_io_runnable + = typename completion_traits< + decltype(std::declval().await_resume()), + noexcept(std::declval().await_resume()) + >::signature_type; + +template +using completion_tuple_for_io_runnable + = typename completion_traits< + decltype(std::declval().await_resume()), + noexcept(std::declval().await_resume()) + >::result_type; + + +} +} +} + +#endif diff --git a/include/boost/capy/asio/executor_adapter.hpp b/include/boost/capy/asio/executor_adapter.hpp index a68e9332..594b116f 100644 --- a/include/boost/capy/asio/executor_adapter.hpp +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -184,6 +184,7 @@ struct asio_executor_adapter 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) diff --git a/include/boost/capy/asio/spawn.hpp b/include/boost/capy/asio/spawn.hpp new file mode 100644 index 00000000..da8994e5 --- /dev/null +++ b/include/boost/capy/asio/spawn.hpp @@ -0,0 +1,267 @@ +// +// 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_SPAWN_HPP +#define BOOST_CAPY_ASIO_SPAWN_HPP + + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include + + +namespace boost::capy +{ + +namespace detail +{ + +struct boost_asio_init; + + + +template +struct boost_asio_promise_type_allocator_base +{ + template + void * operator new (std::size_t n, boost_asio_init &, + Handler & handler, + Ex & exec, Runnable & r) + { + using allocator_type = std::allocator_traits::template rebind_alloc; + allocator_type allocator(boost::asio::get_associated_allocator(handler)); + using traits = std::allocator_traits; //::rebind_alloc() + + // round n up to max_align + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + auto mem = std::allocator_traits:: + template rebind_traits:: + allocate(allocator, n + sizeof(allocator_type)); + + void* p = static_cast(mem) + n; + new (p) allocator_type(std::move(allocator)); + return mem; + } + void operator delete(void * ptr, std::size_t n) + { + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + using allocator_type = std::allocator_traits::template rebind_alloc; + auto allocator_p = reinterpret_cast(static_cast(ptr) + n); + auto allocator = std::move(allocator_p); + + allocator_p->~allocator_type(); + allocator.deallocate(ptr, n + sizeof(allocator_type)); + } +}; + + +template<> +struct boost_asio_promise_type_allocator_base> +{ +}; + +template +struct boost_asio_init_promise_type + : boost_asio_promise_type_allocator_base> +{ + using args_type = completion_tuple_for_io_runnable; + + boost_asio_init_promise_type(boost_asio_init &, Handler & h, Ex & exec, Runnable & r) + : handler(h), ex(exec) {} + + Handler & handler; + Ex &ex; + + void get_return_object() {} + void unhandled_exception() {throw;} + void return_value() {} + + std::suspend_never initial_suspend() noexcept {return {};} + std::suspend_never final_suspend() noexcept {return {};} + + + struct completer + { + Handler &handler; + asio_executor_adapter ex; + args_type args; + + bool await_ready() const {return false;} + void await_suspend(std::coroutine_handle h) + { + auto h_ = std::move(handler); + auto args_ = std::move(args); + h.destroy(); + + auto handler = + std::apply( + [&](auto ... args) {return boost::asio::append(std::move(h_), std::move(args)...);}, + args_); + + auto exec = boost::asio::get_associated_immediate_executor(handler, ex); + boost::asio::dispatch(exec, std::move(handler)); + } + void await_resume() const {} + }; + + completer yield_value(args_type value) + { + return {handler, ex, std::move(value)}; + } + + struct wrapper + { + Runnable r; + Ex ex; + io_env env; + std::stop_source stop_src; + boost::asio::cancellation_slot cancel_slot; + + continuation c; + + bool await_ready() {return r.await_ready(); } + + std::coroutine_handle<> await_suspend(std::coroutine_handle tr) + { + // always post in + auto h = r.handle(); + auto & p = h.promise(); + p.set_continuation(tr); + env.executor = ex; + + env.stop_token = stop_src.get_token(); + cancel_slot = boost::asio::get_associated_cancellation_slot(tr.promise().handler); + if (cancel_slot.is_connected()) + cancel_slot.assign( + [this](boost::asio::cancellation_type ct) + { + if ((ct & boost::asio::cancellation_type::terminal) + != boost::asio::cancellation_type::none) + stop_src.request_stop(); + }); + env.frame_allocator = get_current_frame_allocator(); + + + p.set_environment(&env); + c.h = h; + return ex.dispatch(c); + } + + completion_tuple_for_io_runnable await_resume() + { + if (cancel_slot.is_connected()) + cancel_slot.clear(); + + using type = decltype(r.await_resume()); + if constexpr (noexcept(r.await_resume())) + { + if constexpr (std::is_void_v) + try + { + r.await_resume(); + return {std::exception_ptr()}; + } + catch (...) + { + return std::current_exception(); + } + else + try + { + return {r.await_resume(), std::exception_ptr()}; + } + catch (...) + { + return {type(), std::current_exception()}; + } + } + else + { + if constexpr (std::is_void_v) + { + r.await_resume(); + return {}; + } + else + return {r.await_resume()}; + } + } + }; + + wrapper await_transform(Runnable & r) + { + return wrapper{std::move(r), std::move(ex)}; + } + +}; + +struct boost_asio_init +{ + + template + void operator()( + Handler h, + Ex executor, + Runnable runnable) + { + auto res = co_await runnable; + co_yield std::move(res); + } + +}; + +} + +template> Token + = boost::asio::default_completion_token_t> +auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +{ + return boost::asio::async_initiate>( + detail::boost_asio_init{}, + token, std::move(exec), std::move(runnable)); +} + + +template> Token + = boost::asio::default_completion_token_t> +auto asio_spawn(Context & ctx, Runnable && runnable, Token token) +{ + + return boost::asio::async_initiate>( + detail::boost_asio_init{}, + token, ctx.get_executor(), std::move(runnable)); +} + + +} + +template +struct std::coroutine_traits +{ + using promise_type = boost::capy::detail::boost_asio_init_promise_type; +}; + +#endif diff --git a/include/boost/capy/asio/standalone_spawn.hpp b/include/boost/capy/asio/standalone_spawn.hpp new file mode 100644 index 00000000..fb556728 --- /dev/null +++ b/include/boost/capy/asio/standalone_spawn.hpp @@ -0,0 +1,267 @@ +// +// 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_STANDALONE_ASIO_SPAWN_HPP +#define BOOST_CAPY_STANDALONE_ASIO_SPAWN_HPP + + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include + + +namespace boost::capy +{ + +namespace detail +{ + +struct standalone_asio_init; + + + +template +struct standalone_asio_promise_type_allocator_base +{ + template + void * operator new (std::size_t n, standalone_asio_init &, + Handler & handler, + Ex & exec, Runnable & r) + { + using allocator_type = std::allocator_traits::template rebind_alloc; + allocator_type allocator(::asio::get_associated_allocator(handler)); + using traits = std::allocator_traits; //::rebind_alloc() + + // round n up to max_align + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + auto mem = std::allocator_traits:: + template rebind_traits:: + allocate(allocator, n + sizeof(allocator_type)); + + void* p = static_cast(mem) + n; + new (p) allocator_type(std::move(allocator)); + return mem; + } + void operator delete(void * ptr, std::size_t n) + { + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + using allocator_type = std::allocator_traits::template rebind_alloc; + auto allocator_p = reinterpret_cast(static_cast(ptr) + n); + auto allocator = std::move(allocator_p); + + allocator_p->~allocator_type(); + allocator.deallocate(ptr, n + sizeof(allocator_type)); + } +}; + + +template<> +struct standalone_asio_promise_type_allocator_base> +{ +}; + +template +struct standalone_asio_init_promise_type + : standalone_asio_promise_type_allocator_base<::asio::associated_allocator_t> +{ + using args_type = completion_tuple_for_io_runnable; + + standalone_asio_init_promise_type(standalone_asio_init &, Handler & h, Ex & exec, Runnable & r) + : handler(h), ex(exec) {} + + Handler & handler; + Ex &ex; + + void get_return_object() {} + void unhandled_exception() {throw;} + void return_value() {} + + std::suspend_never initial_suspend() noexcept {return {};} + std::suspend_never final_suspend() noexcept {return {};} + + + struct completer + { + Handler &handler; + standalone_asio_executor_adapter ex; + args_type args; + + bool await_ready() const {return false;} + void await_suspend(std::coroutine_handle h) + { + auto h_ = std::move(handler); + auto args_ = std::move(args); + h.destroy(); + + auto handler = + std::apply( + [&](auto ... args) {return ::asio::append(std::move(h_), std::move(args)...);}, + args_); + + auto exec = ::asio::get_associated_immediate_executor(handler, ex); + ::asio::dispatch(exec, std::move(handler)); + } + void await_resume() const {} + }; + + completer yield_value(args_type value) + { + return {handler, ex, std::move(value)}; + } + + struct wrapper + { + Runnable r; + Ex ex; + io_env env; + std::stop_source stop_src; + ::asio::cancellation_slot cancel_slot; + + continuation c; + + bool await_ready() {return r.await_ready(); } + + std::coroutine_handle<> await_suspend(std::coroutine_handle tr) + { + // always post in + auto h = r.handle(); + auto & p = h.promise(); + p.set_continuation(tr); + env.executor = ex; + + env.stop_token = stop_src.get_token(); + cancel_slot = ::asio::get_associated_cancellation_slot(tr.promise().handler); + if (cancel_slot.is_connected()) + cancel_slot.assign( + [this](::asio::cancellation_type ct) + { + if ((ct & ::asio::cancellation_type::terminal) + != ::asio::cancellation_type::none) + stop_src.request_stop(); + }); + env.frame_allocator = get_current_frame_allocator(); + + + p.set_environment(&env); + c.h = h; + return ex.dispatch(c); + } + + completion_tuple_for_io_runnable await_resume() + { + if (cancel_slot.is_connected()) + cancel_slot.clear(); + + using type = decltype(r.await_resume()); + if constexpr (noexcept(r.await_resume())) + { + if constexpr (std::is_void_v) + try + { + r.await_resume(); + return {std::exception_ptr()}; + } + catch (...) + { + return std::current_exception(); + } + else + try + { + return {r.await_resume(), std::exception_ptr()}; + } + catch (...) + { + return {type(), std::current_exception()}; + } + } + else + { + if constexpr (std::is_void_v) + { + r.await_resume(); + return {}; + } + else + return {r.await_resume()}; + } + } + }; + + wrapper await_transform(Runnable & r) + { + return wrapper{std::move(r), std::move(ex)}; + } + +}; + +struct standalone_asio_init +{ + + template + void operator()( + Handler h, + Ex executor, + Runnable runnable) + { + auto res = co_await runnable; + co_yield std::move(res); + } + +}; + +} + +template> Token + = ::asio::default_completion_token_t> +auto standalone_asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +{ + return ::asio::async_initiate>( + detail::standalone_asio_init{}, + token, std::move(exec), std::move(runnable)); +} + + +template> Token + = ::asio::default_completion_token_t> +auto standalone_asio_spawn(Context & ctx, Runnable && runnable, Token token) +{ + + return ::asio::async_initiate>( + detail::standalone_asio_init{}, + token, ctx.get_executor(), std::move(runnable)); +} + + +} + +template +struct std::coroutine_traits +{ + using promise_type = boost::capy::detail::standalone_asio_init_promise_type; +}; + +#endif diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp index 82bd6d3b..88b1f090 100644 --- a/test/unit/asio.cpp +++ b/test/unit/asio.cpp @@ -7,26 +7,39 @@ // Official repository: https://github.com/cppalliance/capy // + + + +#include #if __has_include() #include +#include +#include +#include #include #include #include #include +#include #endif #if __has_include() -#include +#include +#include +#include +#include #include #include #include #include +#include #endif - +#include +#include #include #include "test_helpers.hpp" #include "test_suite.hpp" @@ -37,6 +50,7 @@ namespace boost { namespace capy { + #if __has_include() struct asio_standalone_test @@ -74,9 +88,89 @@ struct asio_standalone_test BOOST_TEST_EQ(work_cnt, 0); } + + void testFromExecutor() + { + ::asio::io_context ctx; + boost::capy::executor_from_standalone_asio exec{ctx.get_executor()}; + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + done = true; + co_return ; + }; + + boost::capy::run_async(exec)(tsk()); + BOOST_TEST(!done); + ctx.run(); + BOOST_TEST(done); + } + + + void testFromAnyIOExecutor() + { + ::asio::io_context ctx; + ::asio::any_io_executor any_exec{ctx.get_executor()}; + boost::capy::executor_from_standalone_asio exec{any_exec}; + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + done = true; + co_return ; + }; + + boost::capy::run_async(exec)(tsk()); + BOOST_TEST(!done); + ctx.run(); + BOOST_TEST(done); + } + + void testAsIoAwaitable() + { + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + boost::capy::standalone_asio_executor_adapter wrapped_te{co_await capy::this_coro::executor}; + co_await ::asio::post(wrapped_te, as_io_awaitable); + done = true; + }; + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::run_async(exec)(tsk()); + + BOOST_TEST(done); + } + + + void testAsioSpawn() + { + int dispatch_count = 0; + test_executor exec{0, dispatch_count}; + bool done = false; + auto tsk = [&]() -> + boost::capy::task + { + done = true; + co_return ; + }; + + auto ft = standalone_asio_spawn(exec, tsk(), ::asio::use_future); + + ft.get(); + BOOST_TEST(done); + BOOST_TEST(dispatch_count == 1); + } + void run() { testExecutor(); + testFromExecutor(); + testFromAnyIOExecutor(); + testAsIoAwaitable(); + testAsioSpawn(); } }; @@ -118,10 +212,90 @@ struct boost_asio_test aio = nullptr; BOOST_TEST_EQ(work_cnt, 0); } + + void testFromExecutor() + { + boost::asio::io_context ctx; + boost::capy::executor_from_asio exec{ctx.get_executor()}; + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + done = true; + co_return ; + }; + + boost::capy::run_async(exec)(tsk()); + BOOST_TEST(!done); + ctx.run(); + BOOST_TEST(done); + } + + + void testFromAnyIOExecutor() + { + boost::asio::io_context ctx; + boost::asio::any_io_executor any_exec{ctx.get_executor()}; + boost::capy::executor_from_asio exec{any_exec}; + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + done = true; + co_return ; + }; + + boost::capy::run_async(exec)(tsk()); + BOOST_TEST(!done); + ctx.run(); + BOOST_TEST(done); + } + + void testAsIoAwaitable() + { + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + boost::capy::asio_executor_adapter wrapped_te{co_await capy::this_coro::executor}; + + co_await boost::asio::post(wrapped_te, as_io_awaitable); + done = true; + }; + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::run_async(exec)(tsk()); + + BOOST_TEST(done); + } + + void testAsioSpawn() + { + int dispatch_count = 0; + test_executor exec{0, dispatch_count}; + bool done = false; + auto tsk = [&]() -> + boost::capy::task + { + done = true; + co_return ; + }; + + auto ft = asio_spawn(exec, tsk(), boost::asio::use_future); + + ft.get(); + BOOST_TEST(done); + BOOST_TEST(dispatch_count == 1); + } void run() { testExecutor(); + testFromExecutor(); + testFromAnyIOExecutor(); + testAsIoAwaitable(); + testAsioSpawn(); } }; From 97d531c99de50693cf624b38f41f41894031e29f Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 15 Apr 2026 18:10:36 +0800 Subject: [PATCH 06/29] The same functions types work with boost and standalone asio --- include/boost/capy/asio/as_io_awaitable.hpp | 119 ++-- include/boost/capy/asio/boost.hpp | 533 +++++++++++++++++ .../capy/asio/detail/as_io_awaitable.hpp | 76 --- .../capy/asio/detail/completion_handler.hpp | 98 +++- .../boost/capy/asio/detail/continuation.hpp | 133 +++++ include/boost/capy/asio/detail/fwd.hpp | 52 ++ .../detail/standalone_completion_handler.hpp | 134 ----- include/boost/capy/asio/executor_adapter.hpp | 248 ++------ .../boost/capy/asio/executor_from_asio.hpp | 180 ++---- .../asio/executor_from_standalone_asio.hpp | 228 -------- include/boost/capy/asio/spawn.hpp | 262 ++------- include/boost/capy/asio/standalone.hpp | 538 ++++++++++++++++++ .../capy/asio/standalone_as_io_awaitable.hpp | 92 --- .../capy/asio/standalone_executor_adapter.hpp | 295 ---------- include/boost/capy/asio/standalone_spawn.hpp | 267 --------- include/boost/capy/ex/run_async.hpp | 12 +- test/unit/CMakeLists.txt | 7 + test/unit/asio.cpp | 171 +----- test/unit/asio_both.cpp | 111 ++++ test/unit/asio_standalone.cpp | 162 ++++++ 20 files changed, 1849 insertions(+), 1869 deletions(-) create mode 100644 include/boost/capy/asio/boost.hpp delete mode 100644 include/boost/capy/asio/detail/as_io_awaitable.hpp create mode 100644 include/boost/capy/asio/detail/continuation.hpp create mode 100644 include/boost/capy/asio/detail/fwd.hpp delete mode 100644 include/boost/capy/asio/detail/standalone_completion_handler.hpp delete mode 100644 include/boost/capy/asio/executor_from_standalone_asio.hpp create mode 100644 include/boost/capy/asio/standalone.hpp delete mode 100644 include/boost/capy/asio/standalone_as_io_awaitable.hpp delete mode 100644 include/boost/capy/asio/standalone_executor_adapter.hpp delete mode 100644 include/boost/capy/asio/standalone_spawn.hpp create mode 100644 test/unit/asio_both.cpp create mode 100644 test/unit/asio_standalone.cpp diff --git a/include/boost/capy/asio/as_io_awaitable.hpp b/include/boost/capy/asio/as_io_awaitable.hpp index dd83ab68..15ab56e0 100644 --- a/include/boost/capy/asio/as_io_awaitable.hpp +++ b/include/boost/capy/asio/as_io_awaitable.hpp @@ -1,4 +1,3 @@ -// // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -7,80 +6,72 @@ // Official repository: https://github.com/cppalliance/capy // -#ifndef BOOST_CAPY_ASIO_AS_IO_AWAITABLE -#define BOOST_CAPY_ASIO_AS_IO_AWAITABLE - -#include -#include -#include +#ifndef BOOST_CAPY_ASIO_DETAIL_AS_IO_AWAITABLE_HPP +#define BOOST_CAPY_ASIO_DETAIL_AS_IO_AWAITABLE_HPP -#include +#include +#include - -template -struct boost::asio::async_result +namespace boost::capy { - template - struct awaitable_t - { - cancellation_signal signal; - capy::detail::asio_immediate_executor_helper::completed_immediately_t ci; - struct cb - { - cancellation_signal &signal; - cb(cancellation_signal &signal) : signal(signal) {} - void operator()() {signal.emit(cancellation_type::terminal); } - }; - std::optional> stopper; - - bool await_ready() const {return false;} - void await_suspend(std::coroutine_handle<> h, const capy::io_env * env) - { - stopper.emplace(env->stop_token, signal); - capy::detail::asio_coroutine_completion_handler ch( - h, result_, env, - signal.slot(), - &ci); - - std::apply( - [&](auto ... args) - { - std::move(init_)( - std::move(ch), - std::move(args)...); - }, - std::move(args_)); - - } - - std::tuple await_resume() {return std::move(*result_); } +struct as_io_awaitable_t +{ + /// Default constructor. + constexpr as_io_awaitable_t() + { + } + /// Adapts an executor to add the @c use_op_t completion token as the + /// default. + template + struct executor_with_default : InnerExecutor + { + /// Specify @c use_op_t as the default completion token type. + typedef as_io_awaitable_t default_completion_token_type; - awaitable_t(Initiation init, std::tuple args) - : init_(std::move(init)), args_(std::move(args)) {} - awaitable_t(awaitable_t && rhs) noexcept - : init_(std::move(rhs.init_)), args_(std::move(rhs.args_)), result_(std::move(rhs.result_)) {} - private: - Initiation init_; - std::tuple args_; - std::optional> result_; - - }; + executor_with_default(const InnerExecutor& ex) noexcept + : InnerExecutor(ex) + { + } - template - static auto initiate(Initiation&& initiation, - RawCompletionToken&&, Args&&... args) + /// Construct the adapted executor from the inner executor type. + template + executor_with_default(const InnerExecutor1& ex, + typename std::enable_if< + std::conditional< + !std::is_same::value, + std::is_convertible, + std::false_type + >::type::value>::type = 0) noexcept + : InnerExecutor(ex) { - return awaitable_t< - std::decay_t, - std::decay_t...>( - std::forward(initiation), - std::make_tuple(std::forward(args)...)); } + }; + + /// Type alias to adapt an I/O object to use @c use_op_t as its + /// default completion token type. + template + using as_default_on_t = typename T::template rebind_executor< + executor_with_default >::other; + + /// Function helper to adapt an I/O object to use @c use_op_t as its + /// default completion token type. + template + static typename std::decay_t::template rebind_executor< + executor_with_default::executor_type> + >::other + as_default_on(T && object) + { + return typename std::decay_t::template rebind_executor< + executor_with_default::executor_type> + >::other(std::forward(object)); + } }; +constexpr as_io_awaitable_t as_io_awaitable; -#endif +} +#endif diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp new file mode 100644 index 00000000..f37bf18e --- /dev/null +++ b/include/boost/capy/asio/boost.hpp @@ -0,0 +1,533 @@ +// +// 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_BOOST_HPP +#define BOOST_CAPY_ASIO_BOOST_HPP + + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::capy +{ + + +template +boost::asio::execution_context& + query(const asio_executor_adapter & exec, + boost::asio::execution::context_t) noexcept +{ + using service = detail::asio_adapter_context_service; + return exec.context(). + template use_service(); +} + +template +constexpr boost::asio::execution::blocking_t + query(const asio_executor_adapter & exec, + boost::asio::execution::blocking_t) noexcept +{ + switch (Bits & exec.blocking_mask) + { + case exec.blocking_never: return boost::asio::execution::blocking.never; + case exec.blocking_always: return boost::asio::execution::blocking.always; + case exec.blocking_possibly: return boost::asio::execution::blocking.possibly; + default: return {}; + } +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::blocking_t::possibly_t) +{ + constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; + return asio_executor_adapter(exec); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::blocking_t::never_t) +{ + constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_never; + return asio_executor_adapter(exec); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::blocking_t::always_t) +{ + constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_always; + return asio_executor_adapter(exec); +} + +template +static constexpr boost::asio::execution::outstanding_work_t query( + const asio_executor_adapter & exec, + boost::asio::execution::outstanding_work_t) noexcept +{ + switch (Bits & exec.work_mask) + { + case exec.work_tracked: + return boost::asio::execution::outstanding_work.tracked; + case exec.work_untracked: + return boost::asio::execution::outstanding_work.untracked; + default: return {}; + } +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::outstanding_work_t::tracked_t) +{ + constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; + return asio_executor_adapter(exec); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::outstanding_work_t::untracked_t) +{ + constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_untracked; + return asio_executor_adapter(exec); +} + + +template +constexpr Allocator query( + const asio_executor_adapter & exec, + boost::asio::execution::allocator_t) noexcept +{ + return exec.get_allocator(); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::allocator_t a) +{ + return asio_executor_adapter( + exec, a.value() + ); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + boost::asio::execution::allocator_t a) + noexcept(std::is_nothrow_move_constructible_v) +{ + return asio_executor_adapter, Bits>( + exec, + exec.context().get_frame_allocator() + ); +} + +namespace detail +{ + +template +struct asio_work_tracker_service : boost::asio::execution_context::service +{ + static boost::asio::execution_context::id id; + + asio_work_tracker_service(boost::asio::execution_context & ctx) + : boost::asio::execution_context::service(ctx) {} + + using tracked_executor = + typename boost::asio::prefer_result< + Executor, + boost::asio::execution::outstanding_work_t::tracked_t + >::type; + + alignas(tracked_executor) char buffer[sizeof(tracked_executor) ]; + + std::atomic_size_t work = 0u; + + void shutdown() + { + if (work.exchange(0) > 0u) + reinterpret_cast(buffer)->~tracked_executor(); + } + + + void work_started(const Executor & exec) + { + if (work.fetch_add(1u) == 0u) + new (buffer) tracked_executor( + boost::asio::prefer(exec, + boost::asio::execution::outstanding_work.tracked)); + } + + void work_finished() + { + if (work.fetch_sub(1u) == 1u) + reinterpret_cast(buffer)->~tracked_executor(); + } +}; + + +template +boost::asio::execution_context::id asio_work_tracker_service::id; + + +} + +template +struct asio_boost_standard_executor +{ + + asio_boost_standard_executor(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + asio_boost_standard_executor(asio_boost_standard_executor && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + asio_boost_standard_executor(const asio_boost_standard_executor & 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 + { + auto & ec = boost::asio::query(executor_, boost::asio::execution::context); + boost::asio::use_service>(ec).work_started(executor_); + } + + void on_work_finished() const noexcept + { + auto & ec = boost::asio::query(executor_, boost::asio::execution::context); + boost::asio::use_service>(ec).work_finished(); + } + + + std::coroutine_handle<> dispatch(continuation & c) const + { + boost::asio::prefer( + executor_, + boost::asio::execution::allocator( + std::pmr::polymorphic_allocator( + context().get_frame_allocator() + ) + ) + ).execute(detail::asio_coroutine_unique_handle(c.h)); + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + boost::asio::prefer( + boost::asio::require(executor_, boost::asio::execution::blocking.never), + boost::asio::execution::relationship.fork, + boost::asio::execution::allocator( + std::pmr::polymorphic_allocator( + context().get_frame_allocator() + ) + ) + ).execute(detail::asio_coroutine_unique_handle(c.h)); + } + bool operator==(const asio_boost_standard_executor & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const asio_boost_standard_executor & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + Executor executor_; +}; + + +template> Token> +auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +{ + return asio_spawn(exec, std::forward(runnable))(std::move(token)); +} + +template> Token> +auto asio_spawn(Context & ctx, Runnable && runnable, Token token) +{ + return asio_spawn(ctx.get_executor(), std::forward(runnable))(std::move(token)); +} + +} + +template +struct boost::asio::async_result + : boost::capy::detail::async_result_impl +{ +}; + + +namespace boost::capy::detail +{ + + + +struct boost_asio_init; + +template +struct boost_asio_promise_type_allocator_base +{ + template + void * operator new (std::size_t n, boost_asio_init &, + Handler & handler, + Ex & exec, Runnable & r) + { + using allocator_type = std::allocator_traits::template rebind_alloc; + allocator_type allocator(boost::asio::get_associated_allocator(handler)); + using traits = std::allocator_traits; //::rebind_alloc() + + // round n up to max_align + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + auto mem = std::allocator_traits:: + template rebind_traits:: + allocate(allocator, n + sizeof(allocator_type)); + + void* p = static_cast(mem) + n; + new (p) allocator_type(std::move(allocator)); + return mem; + } + void operator delete(void * ptr, std::size_t n) + { + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + using allocator_type = std::allocator_traits::template rebind_alloc; + auto allocator_p = reinterpret_cast(static_cast(ptr) + n); + auto allocator = std::move(*allocator_p); + + allocator_p->~allocator_type(); + allocator.deallocate(static_cast(ptr), n + sizeof(allocator_type)); + } +}; + + +template +struct boost_asio_init_promise_type + : boost_asio_promise_type_allocator_base> +{ + using args_type = completion_tuple_for_io_runnable; + + boost_asio_init_promise_type(boost_asio_init &, Handler & h, Ex & exec, Runnable & r) + : handler(h), ex(exec) {} + + Handler & handler; + Ex &ex; + + void get_return_object() {} + void unhandled_exception() {throw;} + void return_value() {} + + std::suspend_never initial_suspend() noexcept {return {};} + std::suspend_never final_suspend() noexcept {return {};} + + struct completer + { + Handler handler; + Ex ex; + args_type args; + + bool await_ready() const {return false;} + void await_suspend( + std::coroutine_handle h) + { + auto h_ = std::move(handler); + auto args_ = std::move(args); + asio_executor_adapter ex_ = std::move(ex); + h.destroy(); + + auto handler = + std::apply( + [&](auto ... args) + { + return boost::asio::append(std::move(h_), std::move(args)...); + }, + args_); + + asio_executor_adapter aex(ex); + auto exec = boost::asio::get_associated_immediate_executor(handler, ex_); + boost::asio::dispatch(exec, std::move(handler)); + } + void await_resume() const {} + }; + + completer yield_value(args_type value) + { + return {std::move(handler), std::move(ex), std::move(value)}; + } + + struct wrapper + { + Runnable r; + const Ex &ex; + io_env env; + std::stop_source stop_src; + boost::asio::cancellation_slot cancel_slot; + + continuation c; + + bool await_ready() {return r.await_ready(); } + + std::coroutine_handle<> await_suspend(std::coroutine_handle tr) + { + // always post in + auto h = r.handle(); + auto & p = h.promise(); + p.set_continuation(tr); + env.executor = ex; + + env.stop_token = stop_src.get_token(); + cancel_slot = boost::asio::get_associated_cancellation_slot(tr.promise().handler); + if (cancel_slot.is_connected()) + cancel_slot.assign( + [this](boost::asio::cancellation_type ct) + { + if ((ct & boost::asio::cancellation_type::terminal) + != boost::asio::cancellation_type::none) + stop_src.request_stop(); + }); + env.frame_allocator = get_current_frame_allocator(); + + + p.set_environment(&env); + c.h = h; + return ex.dispatch(c); + } + + completion_tuple_for_io_runnable await_resume() + { + if (cancel_slot.is_connected()) + cancel_slot.clear(); + + using type = decltype(r.await_resume()); + if constexpr (!noexcept(r.await_resume())) + { + if constexpr (std::is_void_v) + try + { + r.await_resume(); + return {std::exception_ptr()}; + } + catch (...) + { + return std::current_exception(); + } + else + try + { + return {std::current_exception(), r.await_resume()}; + } + catch (...) + { + return {std::current_exception(), type()}; + } + } + else + { + if constexpr (std::is_void_v) + { + r.await_resume(); + return {}; + } + else + return {r.await_resume()}; + } + } + }; + + wrapper await_transform(Runnable & r) + { + return wrapper{std::move(r), ex}; + } +}; + + + +struct boost_asio_init +{ + template + void operator()( + Handler h, + Ex executor, + Runnable runnable) + { + auto res = co_await runnable; + co_yield std::move(res); + } +}; + +template + requires + boost::asio::completion_token_for> +struct initialize_asio_spawn_helper +{ + template + static auto init(Executor ex, Runnable r, Token && tk) + { + return boost::asio::async_initiate< + Token, + completion_signature_for_io_runnable>( + boost_asio_init{}, + tk, std::move(ex), std::move(r) + ); + } +}; + +} + + +template +struct std::coroutine_traits +{ + using promise_type = boost::capy::detail::boost_asio_init_promise_type; +}; + +#endif //BOOST_CAPY_ASIO_BOOST_HPP + diff --git a/include/boost/capy/asio/detail/as_io_awaitable.hpp b/include/boost/capy/asio/detail/as_io_awaitable.hpp deleted file mode 100644 index 981f00e1..00000000 --- a/include/boost/capy/asio/detail/as_io_awaitable.hpp +++ /dev/null @@ -1,76 +0,0 @@ -// 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_AS_IO_AWAITABLE_HPP -#define BOOST_CAPY_ASIO_DETAIL_AS_IO_AWAITABLE_HPP - -#include -#include - -namespace boost::capy -{ - - -struct as_io_awaitable_t -{ - /// Default constructor. - constexpr as_io_awaitable_t() - { - } - - /// Adapts an executor to add the @c use_op_t completion token as the - /// default. - template - struct executor_with_default : InnerExecutor - { - /// Specify @c use_op_t as the default completion token type. - typedef as_io_awaitable_t default_completion_token_type; - - executor_with_default(const InnerExecutor& ex) noexcept - : InnerExecutor(ex) - { - } - - /// Construct the adapted executor from the inner executor type. - template - executor_with_default(const InnerExecutor1& ex, - typename std::enable_if< - std::conditional< - !std::is_same::value, - std::is_convertible, - std::false_type - >::type::value>::type = 0) noexcept - : InnerExecutor(ex) - { - } - }; - - /// Type alias to adapt an I/O object to use @c use_op_t as its - /// default completion token type. - template - using as_default_on_t = typename T::template rebind_executor< - executor_with_default >::other; - - /// Function helper to adapt an I/O object to use @c use_op_t as its - /// default completion token type. - template - static typename std::decay_t::template rebind_executor< - executor_with_default::executor_type> - >::other - as_default_on(T && object) - { - return typename std::decay_t::template rebind_executor< - executor_with_default::executor_type> - >::other(std::forward(object)); - } -}; -constexpr as_io_awaitable_t as_io_awaitable; - -} - -#endif diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index ebc779ae..db91b50d 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -15,8 +15,6 @@ #include #include -#include -#include #include #include @@ -25,7 +23,6 @@ namespace boost::capy::detail { - struct asio_immediate_executor_helper { enum completed_immediately_t @@ -33,7 +30,7 @@ struct asio_immediate_executor_helper no, maybe, yes, initiating }; - asio_executor_adapter exec; + executor_ref exec; completed_immediately_t * completed_immediately = nullptr; template @@ -54,7 +51,10 @@ struct asio_immediate_executor_helper } else { - boost::asio::post(exec, std::forward(fn)); + exec.post( + make_continuation( + std::forward(fn), + exec.context().get_frame_allocator())); } } @@ -78,21 +78,13 @@ struct asio_immediate_executor_helper }; -template +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; const capy::io_env * env; - boost::asio::cancellation_slot slot; + CancellationSlot slot; asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; using allocator_type = std::pmr::polymorphic_allocator; @@ -101,7 +93,7 @@ struct asio_coroutine_completion_handler using executor_type = asio_executor_adapter; executor_type get_executor() const {return env->executor;} - using cancellation_slot_type = boost::asio::cancellation_slot; + using cancellation_slot_type = CancellationSlot; cancellation_slot_type get_cancellation_slot() const {return slot;} using immediate_executor_type = asio_immediate_executor_helper; @@ -114,7 +106,7 @@ struct asio_coroutine_completion_handler std::coroutine_handle h, std::optional> & result, const capy::io_env * env, - boost::asio::cancellation_slot slot = {}, + CancellationSlot slot = {}, asio_immediate_executor_helper::completed_immediately_t * ci = nullptr) : handle(h), result(result), env(env), slot(slot), completed_immediately(ci) {} @@ -129,6 +121,78 @@ struct asio_coroutine_completion_handler } }; + +template +struct async_result_impl +{ + + template + struct awaitable_t + { + using completed_immediately_t = capy::detail::asio_immediate_executor_helper::completed_immediately_t; + + CancellationSignal signal; + completed_immediately_t completed_immediately; + struct cb + { + CancellationSignal &signal; + cb(CancellationSignal &signal) : signal(signal) {} + void operator()() {signal.emit(CancellationType::terminal); } + }; + std::optional> stopper; + + bool await_ready() const {return false;} + + bool await_suspend(std::coroutine_handle<> h, const capy::io_env * env) + { + completed_immediately = capy::detail::asio_immediate_executor_helper::completed_immediately_t::initiating; + stopper.emplace(env->stop_token, signal); + using slot_t = decltype(CancellationSignal().slot()); + capy::detail::asio_coroutine_completion_handler ch( + h, result_, env, + signal.slot(), + &completed_immediately); + + std::apply( + [&](auto ... args) + { + std::move(init_)( + std::move(ch), + std::move(args)...); + }, + std::move(args_)); + + if (completed_immediately == completed_immediately_t::initiating) + completed_immediately = completed_immediately_t::no; + return completed_immediately != completed_immediately_t::yes; + } + + std::tuple await_resume() {return std::move(*result_); } + + + awaitable_t(Initiation init, std::tuple args) + : init_(std::move(init)), args_(std::move(args)) {} + awaitable_t(awaitable_t && rhs) noexcept + : init_(std::move(rhs.init_)), args_(std::move(rhs.args_)), result_(std::move(rhs.result_)) {} + private: + Initiation init_; + std::tuple args_; + std::optional> result_; + }; + + template + static auto initiate(Initiation&& initiation, + RawCompletionToken&&, Args&&... args) + { + return awaitable_t< + std::decay_t, + std::decay_t...>( + std::forward(initiation), + std::make_tuple(std::forward(args)...)); + } +}; + + } #endif //BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER diff --git a/include/boost/capy/asio/detail/continuation.hpp b/include/boost/capy/asio/detail/continuation.hpp new file mode 100644 index 00000000..556ba792 --- /dev/null +++ b/include/boost/capy/asio/detail/continuation.hpp @@ -0,0 +1,133 @@ +// +// 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_CONTINUATION_HPP +#define BOOST_CAPY_ASIO_CONTINUATION_HPP + +#include +#include + +#include + +namespace boost::capy +{ + +namespace detail +{ + +template +struct continuation_handle_promise_base_ +{ + using alloc_t = std::allocator_traits + ::template rebind_alloc; + + template + void * operator new(std::size_t n, Func &, Allocator &allocator_) + { + alloc_t alloc(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); + } +}; + + +template<> +struct continuation_handle_promise_base_> +{ +}; + +template +struct continuation_handle_promise_type : continuation_handle_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() + { + using handle_t = std::coroutine_handle; + cont.h = handle_t::from_promise(*this); + cont.next = nullptr; + return cont; + } +}; + +template +struct continuation_helper +{ + capy::continuation &cont; + continuation_helper(continuation & cont) noexcept : cont(cont) {} + using promise_type = continuation_handle_promise_type; +}; + +template Function, typename Allocator> +continuation_helper make_continuation_helper(Function func, Allocator alloc) +{ + co_yield func; +} + +template Function, typename Allocator> +continuation & make_continuation( + Function && func, + Allocator && alloc) +{ + return detail::make_continuation_helper( + std::forward(func), + std::forward(alloc)).cont; +} + +} + + +} + +#endif + diff --git a/include/boost/capy/asio/detail/fwd.hpp b/include/boost/capy/asio/detail/fwd.hpp new file mode 100644 index 00000000..73647b6e --- /dev/null +++ b/include/boost/capy/asio/detail/fwd.hpp @@ -0,0 +1,52 @@ +// +// 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_FWD_HPP +#define BOOST_CAPY_ASIO_DETAIL_FWD_HPP + +namespace boost::asio +{ + +struct execution_context; + +namespace execution::detail +{ + +template +struct context_t; + +} + +template +struct query_result; + +} + + +namespace asio +{ + +struct execution_context; + +namespace execution::detail +{ + +template +struct context_t; + +} + +template +struct query_result; + +} + + + +#endif // BOOST_CAPY_ASIO_DETAIL_FWD_HPP diff --git a/include/boost/capy/asio/detail/standalone_completion_handler.hpp b/include/boost/capy/asio/detail/standalone_completion_handler.hpp deleted file mode 100644 index 21c710cf..00000000 --- a/include/boost/capy/asio/detail/standalone_completion_handler.hpp +++ /dev/null @@ -1,134 +0,0 @@ -// -// 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; - const 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, - const 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 index 594b116f..ea179e79 100644 --- a/include/boost/capy/asio/executor_adapter.hpp +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -10,29 +10,25 @@ #ifndef BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP #define BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP +#include #include #include +#include -#include -#include -#include -#include -#include -#include -#include namespace boost { namespace capy { namespace detail { +template struct asio_adapter_context_service : execution_context::service, // shutdown is protected - boost::asio::execution_context + ExecutionContext { asio_adapter_context_service(boost::capy::execution_context & ctx) {} - void shutdown() override {boost::asio::execution_context::shutdown();} + void shutdown() override {ExecutionContext::shutdown();} }; } @@ -40,54 +36,76 @@ struct asio_adapter_context_service template, - typename Blocking = boost::asio::execution::blocking_t::possibly_t, - typename Outstanding = boost::asio::execution::outstanding_work_t::untracked_t> + int Bits = 0> struct asio_executor_adapter { - template - asio_executor_adapter(const asio_executor_adapter & rhs) + constexpr static int blocking_possibly = 0b000; + constexpr static int blocking_never = 0b001; + constexpr static int blocking_always = 0b010; + constexpr static int blocking_mask = 0b011; + constexpr static int work_untracked = 0b000; + constexpr static int work_tracked = 0b100; + constexpr static int work_mask = 0b100; + + + 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) + if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_started(); } - template - asio_executor_adapter(asio_executor_adapter && rhs) + 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) + if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_started(); } asio_executor_adapter(Executor executor, const Allocator & alloc) - noexcept(std::is_nothrow_move_constructible_v) + noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_copy_constructible_v) : executor_(std::move(executor)), allocator_(alloc) { - if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) - executor_.on_work_started(); + if constexpr((Bits & work_mask) == work_tracked) + executor_.on_work_started(); + } + + template + explicit asio_executor_adapter( + asio_executor_adapter executor, + const Allocator & alloc) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_copy_constructible_v) + : executor_(std::move(executor.executor_)), allocator_(alloc) + { + if constexpr((Bits & work_mask) == 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(); + if constexpr((Bits & work_mask) == work_tracked) + executor_.on_work_started(); } ~asio_executor_adapter() { - if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) - executor_.on_work_finished(); + if constexpr((Bits & work_mask) == work_tracked) + executor_.on_work_finished(); } - template - asio_executor_adapter & operator=(const asio_executor_adapter & rhs) + template + asio_executor_adapter & operator=(const asio_executor_adapter & rhs) { - if constexpr (Outstanding_() == boost::asio::execution::outstanding_work.tracked) + if constexpr((Bits & work_mask) == work_tracked) if (rhs.executor_ != executor_) { rhs.executor_.on_work_started(); @@ -107,182 +125,26 @@ struct asio_executor_adapter { 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) + if constexpr ((Bits & blocking_mask) == blocking_never) + executor_.post(detail::make_continuation(std::forward(f), allocator_)); + else if constexpr((Bits & blocking_mask) == blocking_possibly) + executor_.dispatch(detail::make_continuation(std::forward(f), allocator_)).resume(); + else if constexpr((Bits & blocking_mask) == blocking_always) std::forward(f)(); } + execution_context & context() const {return executor_.context(); } + Allocator get_allocator() const noexcept {return allocator_;} + const Executor get_capy_executor() const {return executor_;} 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 + template friend struct asio_executor_adapter; Executor executor_; [[no_unique_address]] Allocator allocator_; diff --git a/include/boost/capy/asio/executor_from_asio.hpp b/include/boost/capy/asio/executor_from_asio.hpp index e7f7a88f..17108ffd 100644 --- a/include/boost/capy/asio/executor_from_asio.hpp +++ b/include/boost/capy/asio/executor_from_asio.hpp @@ -11,125 +11,60 @@ #define BOOST_CAPY_ASIO_EXECUTOR_FROM_ASIO_HPP #include -#include +#include +#include #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 +namespace detail { - 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(); - } +template +concept AsioNetTsExecutor = requires (Executor exec, + std::coroutine_handle<> h, + std::pmr::polymorphic_allocator a) + { + exec.on_work_started(); + exec.on_work_finished(); + exec.dispatch(h, a); + exec.post(h, a); + exec.context(); + } ; - void post(continuation & c) const - { - boost::asio::post( - executor_, - detail::asio_coroutine_unique_handle(c.h) - ); - } +template +concept AsioBoostStandardExecutor = std::same_as>::type, + boost::asio::execution_context&>; - 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_; - } +template +concept AsioStandaloneStandardExecutor = std::same_as>::type, + ::asio::execution_context&>; - private: - mutable Executor executor_; -}; +} -template - requires requires (Executor exec) - { - exec.on_work_started(); - exec.on_work_finished(); - } -struct executor_from_asio_net_ts +template +struct asio_net_ts_executor { - executor_from_asio_net_ts(Executor executor) + asio_net_ts_executor(Executor executor) noexcept(std::is_nothrow_move_constructible_v) : executor_(std::move(executor)) { } - executor_from_asio_net_ts(executor_from_asio_net_ts && rhs) + asio_net_ts_executor(asio_net_ts_executor && 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) + asio_net_ts_executor(const asio_net_ts_executor & rhs) noexcept(std::is_nothrow_copy_constructible_v) : executor_(rhs.executor_) { @@ -137,7 +72,8 @@ struct executor_from_asio_net_ts execution_context& context() const noexcept { - return boost::asio::use_service> + using ex_t = std::remove_reference_t; + return use_service> ( executor_.context() ); @@ -171,11 +107,11 @@ struct executor_from_asio_net_ts boost::capy::get_current_frame_allocator())); } - bool operator==(const executor_from_asio_net_ts & rhs) const noexcept + bool operator==(const asio_net_ts_executor & rhs) const noexcept { return executor_ == rhs.executor_; } - bool operator!=(const executor_from_asio_net_ts & rhs) const noexcept + bool operator!=(const asio_net_ts_executor & rhs) const noexcept { return executor_ != rhs.executor_; } @@ -185,40 +121,32 @@ struct executor_from_asio_net_ts }; -namespace detail -{ +template +struct asio_boost_standard_executor; +template +struct asio_standalone_standard_executor; -struct executor_from_asio_net_ts_helper -{ - template - using impl = executor_from_asio_net_ts; -}; -struct executor_from_asio_properties_helper +template +auto wrap_asio_executor(Executor && exec) { - template - using impl = executor_from_asio_properties; + using executor_t = std::decay_t; + if constexpr (detail::AsioNetTsExecutor) + return asio_net_ts_executor(std::forward(exec)); + else if constexpr (detail::AsioBoostStandardExecutor) + return asio_boost_standard_executor(std::forward(exec)); + else if constexpr (detail::AsioStandaloneStandardExecutor) + return asio_standalone_standard_executor(std::forward(exec)); + else + static_assert(sizeof(Executor) == 0, "Unknown executor type"); }; -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 +using wrap_asio_executor_t = decltype(wrap_asio_executor(std::declval())); -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; } } diff --git a/include/boost/capy/asio/executor_from_standalone_asio.hpp b/include/boost/capy/asio/executor_from_standalone_asio.hpp deleted file mode 100644 index b10adfb1..00000000 --- a/include/boost/capy/asio/executor_from_standalone_asio.hpp +++ /dev/null @@ -1,228 +0,0 @@ -// -// 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/spawn.hpp b/include/boost/capy/asio/spawn.hpp index da8994e5..082f8bb1 100644 --- a/include/boost/capy/asio/spawn.hpp +++ b/include/boost/capy/asio/spawn.hpp @@ -19,249 +19,83 @@ #include -#include -#include -#include -#include -#include -#include - - namespace boost::capy { namespace detail { -struct boost_asio_init; - +template +struct initialize_asio_spawn_helper; -template -struct boost_asio_promise_type_allocator_base -{ - template - void * operator new (std::size_t n, boost_asio_init &, - Handler & handler, - Ex & exec, Runnable & r) +template +concept asio_spawn_token = + requires (Token && tk, Executor ex, Runnable rn) { - using allocator_type = std::allocator_traits::template rebind_alloc; - allocator_type allocator(boost::asio::get_associated_allocator(handler)); - using traits = std::allocator_traits; //::rebind_alloc() - - // round n up to max_align - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) - n += (sizeof(std::max_align_t) - d); + initialize_asio_spawn_helper:: + init(std::move(ex), std::move(rn), std::forward(tk)); + }; - auto mem = std::allocator_traits:: - template rebind_traits:: - allocate(allocator, n + sizeof(allocator_type)); +template +struct initialize_asio_standalone_spawn_helper; - void* p = static_cast(mem) + n; - new (p) allocator_type(std::move(allocator)); - return mem; - } - void operator delete(void * ptr, std::size_t n) +template +concept asio_standalone_spawn_token = + requires (Token && tk, Executor ex, Runnable rn) { - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) - n += (sizeof(std::max_align_t) - d); - - using allocator_type = std::allocator_traits::template rebind_alloc; - auto allocator_p = reinterpret_cast(static_cast(ptr) + n); - auto allocator = std::move(allocator_p); + initialize_asio_standalone_spawn_helper:: + init(std::move(ex), std::move(rn), std::forward(tk)); + }; - allocator_p->~allocator_type(); - allocator.deallocate(ptr, n + sizeof(allocator_type)); - } -}; +} -template<> -struct boost_asio_promise_type_allocator_base> -{ -}; - -template -struct boost_asio_init_promise_type - : boost_asio_promise_type_allocator_base> +template +struct asio_spawn_op { - using args_type = completion_tuple_for_io_runnable; - - boost_asio_init_promise_type(boost_asio_init &, Handler & h, Ex & exec, Runnable & r) - : handler(h), ex(exec) {} - - Handler & handler; - Ex &ex; - - void get_return_object() {} - void unhandled_exception() {throw;} - void return_value() {} - - std::suspend_never initial_suspend() noexcept {return {};} - std::suspend_never final_suspend() noexcept {return {};} + asio_spawn_op(Executor executor, Runnable runnable) + : executor_(std::move(executor)), runnable_(std::move(runnable)) + {} + template Token> + auto operator()(Token && token) + { + return detail::initialize_asio_spawn_helper::init( + std::move(executor_), + std::move(runnable_), + std::forward(token) + ); + } - struct completer - { - Handler &handler; - asio_executor_adapter ex; - args_type args; - - bool await_ready() const {return false;} - void await_suspend(std::coroutine_handle h) - { - auto h_ = std::move(handler); - auto args_ = std::move(args); - h.destroy(); - - auto handler = - std::apply( - [&](auto ... args) {return boost::asio::append(std::move(h_), std::move(args)...);}, - args_); - - auto exec = boost::asio::get_associated_immediate_executor(handler, ex); - boost::asio::dispatch(exec, std::move(handler)); - } - void await_resume() const {} - }; - - completer yield_value(args_type value) - { - return {handler, ex, std::move(value)}; - } - - struct wrapper - { - Runnable r; - Ex ex; - io_env env; - std::stop_source stop_src; - boost::asio::cancellation_slot cancel_slot; - - continuation c; - - bool await_ready() {return r.await_ready(); } - - std::coroutine_handle<> await_suspend(std::coroutine_handle tr) - { - // always post in - auto h = r.handle(); - auto & p = h.promise(); - p.set_continuation(tr); - env.executor = ex; - - env.stop_token = stop_src.get_token(); - cancel_slot = boost::asio::get_associated_cancellation_slot(tr.promise().handler); - if (cancel_slot.is_connected()) - cancel_slot.assign( - [this](boost::asio::cancellation_type ct) - { - if ((ct & boost::asio::cancellation_type::terminal) - != boost::asio::cancellation_type::none) - stop_src.request_stop(); - }); - env.frame_allocator = get_current_frame_allocator(); - - - p.set_environment(&env); - c.h = h; - return ex.dispatch(c); - } - - completion_tuple_for_io_runnable await_resume() - { - if (cancel_slot.is_connected()) - cancel_slot.clear(); - - using type = decltype(r.await_resume()); - if constexpr (noexcept(r.await_resume())) - { - if constexpr (std::is_void_v) - try - { - r.await_resume(); - return {std::exception_ptr()}; - } - catch (...) - { - return std::current_exception(); - } - else - try - { - return {r.await_resume(), std::exception_ptr()}; - } - catch (...) - { - return {type(), std::current_exception()}; - } - } - else - { - if constexpr (std::is_void_v) - { - r.await_resume(); - return {}; - } - else - return {r.await_resume()}; - } - } - }; - - wrapper await_transform(Runnable & r) - { - return wrapper{std::move(r), std::move(ex)}; - } - -}; - -struct boost_asio_init -{ - - template - void operator()( - Handler h, - Ex executor, - Runnable runnable) + template Token> + auto operator()(Token && token) { - auto res = co_await runnable; - co_yield std::move(res); + return detail::initialize_asio_standalone_spawn_helper::init( + std::move(executor_), + std::move(runnable_), + std::forward(token) + ); } - + + private: + Executor executor_; + Runnable runnable_; }; -} -template> Token - = boost::asio::default_completion_token_t> -auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +template +auto asio_spawn(ExecutorType exec, Runnable && runnable) { - return boost::asio::async_initiate>( - detail::boost_asio_init{}, - token, std::move(exec), std::move(runnable)); + return asio_spawn_op(std::move(exec), std::forward(runnable)); } - -template> Token - = boost::asio::default_completion_token_t> -auto asio_spawn(Context & ctx, Runnable && runnable, Token token) +template +auto asio_spawn(Context & ctx, Runnable && runnable) { - - return boost::asio::async_initiate>( - detail::boost_asio_init{}, - token, ctx.get_executor(), std::move(runnable)); + return asio_spawn_op(ctx.get_executor(), std::forward(runnable)); } - } -template -struct std::coroutine_traits -{ - using promise_type = boost::capy::detail::boost_asio_init_promise_type; -}; - #endif diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp new file mode 100644 index 00000000..1c547c1c --- /dev/null +++ b/include/boost/capy/asio/standalone.hpp @@ -0,0 +1,538 @@ +// +// 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_HPP +#define BOOST_CAPY_ASIO_STANDALONE_HPP + + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::capy +{ + + +template +::asio::execution_context& + query(const asio_executor_adapter & exec, + ::asio::execution::context_t) noexcept +{ + using service = detail::asio_adapter_context_service<::asio::execution_context>; + return exec.context(). + template use_service(); +} + +template +constexpr ::asio::execution::blocking_t + query(const asio_executor_adapter & exec, + ::asio::execution::blocking_t) noexcept +{ + switch (Bits & exec.blocking_mask) + { + case exec.blocking_never: return ::asio::execution::blocking.never; + case exec.blocking_always: return ::asio::execution::blocking.always; + case exec.blocking_possibly: return ::asio::execution::blocking.possibly; + default: return {}; + } +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::blocking_t::possibly_t) +{ + constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; + return asio_executor_adapter(exec); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::blocking_t::never_t) +{ + constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_never; + return asio_executor_adapter(exec); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::blocking_t::always_t) +{ + constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_always; + return asio_executor_adapter(exec); +} + +template +static constexpr ::asio::execution::outstanding_work_t query( + const asio_executor_adapter & exec, + ::asio::execution::outstanding_work_t) noexcept +{ + switch (Bits & exec.work_mask) + { + case exec.work_tracked: + return ::asio::execution::outstanding_work.tracked; + case exec.work_untracked: + return ::asio::execution::outstanding_work.untracked; + default: return {}; + } +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::outstanding_work_t::tracked_t) +{ + constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; + return asio_executor_adapter(exec); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::outstanding_work_t::untracked_t) +{ + constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_untracked; + return asio_executor_adapter(exec); +} + + +template +constexpr Allocator query( + const asio_executor_adapter & exec, + ::asio::execution::allocator_t) noexcept +{ + return exec.get_allocator(); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::allocator_t a) +{ + return asio_executor_adapter( + exec, a.value() + ); +} + +template +constexpr auto + require(const asio_executor_adapter & exec, + ::asio::execution::allocator_t a) + noexcept(std::is_nothrow_move_constructible_v) +{ + return asio_executor_adapter, Bits>( + exec, + exec.context().get_frame_allocator() + ); +} + +namespace detail +{ + +template +struct asio_standalone_work_tracker_service : ::asio::execution_context::service +{ + static ::asio::execution_context::id id; + + asio_standalone_work_tracker_service(::asio::execution_context & ctx) + : ::asio::execution_context::service(ctx) {} + + using tracked_executor = + typename ::asio::prefer_result< + Executor, + ::asio::execution::outstanding_work_t::tracked_t + >::type; + + alignas(tracked_executor) char buffer[sizeof(tracked_executor) ]; + + std::atomic_size_t work = 0u; + + void shutdown() + { + if (work.exchange(0) > 0u) + reinterpret_cast(buffer)->~tracked_executor(); + } + + + void work_started(const Executor & exec) + { + if (work.fetch_add(1u) == 0u) + new (buffer) tracked_executor( + ::asio::prefer(exec, + ::asio::execution::outstanding_work.tracked)); + } + + void work_finished() + { + if (work.fetch_sub(1u) == 1u) + reinterpret_cast(buffer)->~tracked_executor(); + } +}; + + +template +::asio::execution_context::id asio_standalone_work_tracker_service::id; + + +} + +template +struct asio_standalone_standard_executor +{ + + asio_standalone_standard_executor(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + asio_standalone_standard_executor(asio_standalone_standard_executor && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + asio_standalone_standard_executor(const asio_standalone_standard_executor & 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 + { + auto & ec = ::asio::query(executor_, ::asio::execution::context); + ::asio::use_service>(ec).work_started(executor_); + } + + void on_work_finished() const noexcept + { + auto & ec = ::asio::query(executor_, ::asio::execution::context); + ::asio::use_service>(ec).work_finished(); + } + + + std::coroutine_handle<> dispatch(continuation & c) const + { + ::asio::prefer( + executor_, + ::asio::execution::allocator( + std::pmr::polymorphic_allocator( + context().get_frame_allocator() + ) + ) + ).execute(detail::asio_coroutine_unique_handle(c.h)); + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + ::asio::prefer( + ::asio::require(executor_, ::asio::execution::blocking.never), + ::asio::execution::relationship.fork, + ::asio::execution::allocator( + std::pmr::polymorphic_allocator( + context().get_frame_allocator() + ) + ) + ).execute(detail::asio_coroutine_unique_handle(c.h)); + } + bool operator==(const asio_standalone_standard_executor & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const asio_standalone_standard_executor & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + Executor executor_; +}; + + +template> Token> +auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +{ + return asio_spawn(exec, std::forward(runnable))(std::move(token)); +} + +template> Token> +auto asio_spawn(Context & ctx, Runnable && runnable, Token token) +{ + return asio_spawn(ctx.get_executor(), std::forward(runnable))(std::move(token)); +} + +} + +template +struct asio::async_result + : boost::capy::detail::async_result_impl<::asio::cancellation_signal, ::asio::cancellation_type, Ts...> +{ +}; + + +namespace boost::capy::detail +{ + + + +struct boost_asio_standalone_init; + +template +struct boost_asio_standalone_promise_type_allocator_base +{ + template + void * operator new (std::size_t n, boost_asio_standalone_init &, + Handler & handler, + Ex & exec, Runnable & r) + { + using allocator_type = std::allocator_traits::template rebind_alloc; + allocator_type allocator(::asio::get_associated_allocator(handler)); + using traits = std::allocator_traits; //::rebind_alloc() + + // round n up to max_align + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + auto mem = std::allocator_traits:: + template rebind_traits:: + allocate(allocator, n + sizeof(allocator_type)); + + void* p = static_cast(mem) + n; + new (p) allocator_type(std::move(allocator)); + return mem; + } + void operator delete(void * ptr, std::size_t n) + { + if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + n += (sizeof(std::max_align_t) - d); + + using allocator_type = std::allocator_traits::template rebind_alloc; + auto allocator_p = reinterpret_cast(static_cast(ptr) + n); + auto allocator = std::move(*allocator_p); + + allocator_p->~allocator_type(); + allocator.deallocate(static_cast(ptr), n + sizeof(allocator_type)); + } +}; + + +template +struct boost_asio_standalone_init_promise_type + : boost_asio_standalone_promise_type_allocator_base<::asio::associated_allocator_t> +{ + using args_type = completion_tuple_for_io_runnable; + + boost_asio_standalone_init_promise_type( + boost_asio_standalone_init &, + Handler & h, + Ex & exec, + Runnable & r) + : handler(h), ex(exec) {} + + Handler & handler; + Ex &ex; + + void get_return_object() {} + void unhandled_exception() {throw;} + void return_value() {} + + std::suspend_never initial_suspend() noexcept {return {};} + std::suspend_never final_suspend() noexcept {return {};} + + struct completer + { + Handler handler; + Ex ex; + args_type args; + + bool await_ready() const {return false;} + void await_suspend( + std::coroutine_handle h) + { + auto h_ = std::move(handler); + auto args_ = std::move(args); + asio_executor_adapter ex_ = std::move(ex); + h.destroy(); + + auto handler = + std::apply( + [&](auto ... args) + { + return ::asio::append(std::move(h_), std::move(args)...); + }, + args_); + + asio_executor_adapter aex(ex); + auto exec = ::asio::get_associated_immediate_executor(handler, ex_); + ::asio::dispatch(exec, std::move(handler)); + } + void await_resume() const {} + }; + + completer yield_value(args_type value) + { + return {std::move(handler), std::move(ex), std::move(value)}; + } + + struct wrapper + { + Runnable r; + const Ex &ex; + io_env env; + std::stop_source stop_src; + ::asio::cancellation_slot cancel_slot; + + continuation c; + + bool await_ready() {return r.await_ready(); } + + std::coroutine_handle<> await_suspend( + std::coroutine_handle tr) + { + // always post in + auto h = r.handle(); + auto & p = h.promise(); + p.set_continuation(tr); + env.executor = ex; + + env.stop_token = stop_src.get_token(); + cancel_slot = ::asio::get_associated_cancellation_slot(tr.promise().handler); + if (cancel_slot.is_connected()) + cancel_slot.assign( + [this](::asio::cancellation_type ct) + { + if ((ct & ::asio::cancellation_type::terminal) + != ::asio::cancellation_type::none) + stop_src.request_stop(); + }); + env.frame_allocator = get_current_frame_allocator(); + + + p.set_environment(&env); + c.h = h; + return ex.dispatch(c); + } + + completion_tuple_for_io_runnable await_resume() + { + if (cancel_slot.is_connected()) + cancel_slot.clear(); + + using type = decltype(r.await_resume()); + if constexpr (!noexcept(r.await_resume())) + { + if constexpr (std::is_void_v) + try + { + r.await_resume(); + return {std::exception_ptr()}; + } + catch (...) + { + return std::current_exception(); + } + else + try + { + return {std::exception_ptr(), r.await_resume()}; + } + catch (...) + { + return {std::current_exception(), type()}; + } + } + else + { + if constexpr (std::is_void_v) + { + r.await_resume(); + return {}; + } + else + return {r.await_resume()}; + } + } + }; + + wrapper await_transform(Runnable & r) + { + return wrapper{std::move(r), ex}; + } +}; + + + +struct boost_asio_standalone_init +{ + template + void operator()( + Handler h, + Ex executor, + Runnable runnable) + { + auto res = co_await runnable; + co_yield std::move(res); + } +}; + +template + requires + ::asio::completion_token_for> +struct initialize_asio_standalone_spawn_helper +{ + template + static auto init(Executor ex, Runnable r, Token && tk) + { + return ::asio::async_initiate< + Token, + completion_signature_for_io_runnable>( + boost_asio_standalone_init{}, + tk, std::move(ex), std::move(r) + ); + } +}; + +} + + +template +struct std::coroutine_traits +{ + using promise_type = boost::capy::detail::boost_asio_standalone_init_promise_type; +}; + +#endif // BOOST_CAPY_ASIO_STANDALONE_HPP + diff --git a/include/boost/capy/asio/standalone_as_io_awaitable.hpp b/include/boost/capy/asio/standalone_as_io_awaitable.hpp deleted file mode 100644 index 4a65932a..00000000 --- a/include/boost/capy/asio/standalone_as_io_awaitable.hpp +++ /dev/null @@ -1,92 +0,0 @@ -// -// 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_AS_IO_AWAITABLE -#define BOOST_CAPY_ASIO_STANDALONE_AS_IO_AWAITABLE - -#include -#include -#include - -#include - -template -struct asio::async_result -{ - template - struct awaitable_t - { - cancellation_signal signal; - boost::capy::detail::standalone_asio_immediate_executor_helper::completed_immediately_t ci; - - struct cb - { - cancellation_signal &signal; - cb(cancellation_signal &signal) : signal(signal) {} - void operator()() {signal.emit(cancellation_type::terminal); } - }; - std::optional> stopper; - - bool await_ready() const {return false;} - - bool await_suspend(std::coroutine_handle<> h, const boost::capy::io_env * env) - { - stopper.emplace(env->stop_token, signal); - boost::capy::detail::standalone_asio_coroutine_completion_handler ch( - h, result_, env, - signal.slot(), - &ci); - - using ci_t = boost::capy::detail::standalone_asio_immediate_executor_helper::completed_immediately_t; - ci = ci_t::initiating; - - - std::apply( - [&](auto ... args) - { - std::move(init_)( - std::move(ch), - std::move(args)...); - }, - std::move(args_)); - - if (ci == ci_t::initiating) - ci = ci_t::no; - return ci != ci_t::yes; - } - - std::tuple await_resume() {return std::move(*result_); } - - - awaitable_t(Initiation init, std::tuple args) - : init_(std::move(init)), args_(std::move(args)) {} - - awaitable_t(awaitable_t && rhs) noexcept - : init_(std::move(rhs.init_)), args_(std::move(rhs.args_)), result_(std::move(rhs.result_)) {} private: - Initiation init_; - std::tuple args_; - std::optional> result_; - - }; - - template - static auto initiate(Initiation&& initiation, - RawCompletionToken&& token, Args&&... args) - { - return awaitable_t< - std::decay_t, - std::decay_t...>( - std::forward(initiation), - std::make_tuple(std::forward(args)...)); - } -}; - - -#endif - diff --git a/include/boost/capy/asio/standalone_executor_adapter.hpp b/include/boost/capy/asio/standalone_executor_adapter.hpp deleted file mode 100644 index 978e3949..00000000 --- a/include/boost/capy/asio/standalone_executor_adapter.hpp +++ /dev/null @@ -1,295 +0,0 @@ -// -// 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/include/boost/capy/asio/standalone_spawn.hpp b/include/boost/capy/asio/standalone_spawn.hpp deleted file mode 100644 index fb556728..00000000 --- a/include/boost/capy/asio/standalone_spawn.hpp +++ /dev/null @@ -1,267 +0,0 @@ -// -// 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_STANDALONE_ASIO_SPAWN_HPP -#define BOOST_CAPY_STANDALONE_ASIO_SPAWN_HPP - - -#include -#include -#include -#include -#include -#include - - -#include -#include -#include -#include -#include -#include - - -namespace boost::capy -{ - -namespace detail -{ - -struct standalone_asio_init; - - - -template -struct standalone_asio_promise_type_allocator_base -{ - template - void * operator new (std::size_t n, standalone_asio_init &, - Handler & handler, - Ex & exec, Runnable & r) - { - using allocator_type = std::allocator_traits::template rebind_alloc; - allocator_type allocator(::asio::get_associated_allocator(handler)); - using traits = std::allocator_traits; //::rebind_alloc() - - // round n up to max_align - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) - n += (sizeof(std::max_align_t) - d); - - auto mem = std::allocator_traits:: - template rebind_traits:: - allocate(allocator, n + sizeof(allocator_type)); - - void* p = static_cast(mem) + n; - new (p) allocator_type(std::move(allocator)); - return mem; - } - void operator delete(void * ptr, std::size_t n) - { - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) - n += (sizeof(std::max_align_t) - d); - - using allocator_type = std::allocator_traits::template rebind_alloc; - auto allocator_p = reinterpret_cast(static_cast(ptr) + n); - auto allocator = std::move(allocator_p); - - allocator_p->~allocator_type(); - allocator.deallocate(ptr, n + sizeof(allocator_type)); - } -}; - - -template<> -struct standalone_asio_promise_type_allocator_base> -{ -}; - -template -struct standalone_asio_init_promise_type - : standalone_asio_promise_type_allocator_base<::asio::associated_allocator_t> -{ - using args_type = completion_tuple_for_io_runnable; - - standalone_asio_init_promise_type(standalone_asio_init &, Handler & h, Ex & exec, Runnable & r) - : handler(h), ex(exec) {} - - Handler & handler; - Ex &ex; - - void get_return_object() {} - void unhandled_exception() {throw;} - void return_value() {} - - std::suspend_never initial_suspend() noexcept {return {};} - std::suspend_never final_suspend() noexcept {return {};} - - - struct completer - { - Handler &handler; - standalone_asio_executor_adapter ex; - args_type args; - - bool await_ready() const {return false;} - void await_suspend(std::coroutine_handle h) - { - auto h_ = std::move(handler); - auto args_ = std::move(args); - h.destroy(); - - auto handler = - std::apply( - [&](auto ... args) {return ::asio::append(std::move(h_), std::move(args)...);}, - args_); - - auto exec = ::asio::get_associated_immediate_executor(handler, ex); - ::asio::dispatch(exec, std::move(handler)); - } - void await_resume() const {} - }; - - completer yield_value(args_type value) - { - return {handler, ex, std::move(value)}; - } - - struct wrapper - { - Runnable r; - Ex ex; - io_env env; - std::stop_source stop_src; - ::asio::cancellation_slot cancel_slot; - - continuation c; - - bool await_ready() {return r.await_ready(); } - - std::coroutine_handle<> await_suspend(std::coroutine_handle tr) - { - // always post in - auto h = r.handle(); - auto & p = h.promise(); - p.set_continuation(tr); - env.executor = ex; - - env.stop_token = stop_src.get_token(); - cancel_slot = ::asio::get_associated_cancellation_slot(tr.promise().handler); - if (cancel_slot.is_connected()) - cancel_slot.assign( - [this](::asio::cancellation_type ct) - { - if ((ct & ::asio::cancellation_type::terminal) - != ::asio::cancellation_type::none) - stop_src.request_stop(); - }); - env.frame_allocator = get_current_frame_allocator(); - - - p.set_environment(&env); - c.h = h; - return ex.dispatch(c); - } - - completion_tuple_for_io_runnable await_resume() - { - if (cancel_slot.is_connected()) - cancel_slot.clear(); - - using type = decltype(r.await_resume()); - if constexpr (noexcept(r.await_resume())) - { - if constexpr (std::is_void_v) - try - { - r.await_resume(); - return {std::exception_ptr()}; - } - catch (...) - { - return std::current_exception(); - } - else - try - { - return {r.await_resume(), std::exception_ptr()}; - } - catch (...) - { - return {type(), std::current_exception()}; - } - } - else - { - if constexpr (std::is_void_v) - { - r.await_resume(); - return {}; - } - else - return {r.await_resume()}; - } - } - }; - - wrapper await_transform(Runnable & r) - { - return wrapper{std::move(r), std::move(ex)}; - } - -}; - -struct standalone_asio_init -{ - - template - void operator()( - Handler h, - Ex executor, - Runnable runnable) - { - auto res = co_await runnable; - co_yield std::move(res); - } - -}; - -} - -template> Token - = ::asio::default_completion_token_t> -auto standalone_asio_spawn(ExecutorType exec, Runnable && runnable, Token token) -{ - return ::asio::async_initiate>( - detail::standalone_asio_init{}, - token, std::move(exec), std::move(runnable)); -} - - -template> Token - = ::asio::default_completion_token_t> -auto standalone_asio_spawn(Context & ctx, Runnable && runnable, Token token) -{ - - return ::asio::async_initiate>( - detail::standalone_asio_init{}, - token, ctx.get_executor(), std::move(runnable)); -} - - -} - -template -struct std::coroutine_traits -{ - using promise_type = boost::capy::detail::standalone_asio_init_promise_type; -}; - -#endif diff --git a/include/boost/capy/ex/run_async.hpp b/include/boost/capy/ex/run_async.hpp index 6f1b061c..2b142ef3 100644 --- a/include/boost/capy/ex/run_async.hpp +++ b/include/boost/capy/ex/run_async.hpp @@ -419,6 +419,16 @@ class [[nodiscard]] run_async_wrapper p.wg_.executor().dispatch(p.task_cont_).resume(); } }; +/* + +template +concept value_handler = std::invocable || + std::invocable().await_resume())> + +template +concept error_handler = std::invocable; + +*/ // Executor only (uses default recycling allocator) @@ -491,7 +501,7 @@ run_async(Ex ex) @see task @see executor */ -template +template [[nodiscard]] auto run_async(Ex ex, H1 h1) { diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 14774345..24b36205 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -12,6 +12,12 @@ list(APPEND PFILES CMakeLists.txt Jamfile) +# TODO REMOVE +include_directories(/home/klemens/develop/asio/include /home/klemens/develop/boost) + + + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) add_executable(boost_capy_tests ${PFILES}) @@ -20,6 +26,7 @@ target_link_libraries( Boost::capy_test_suite_main Boost::capy) +set_property(TARGET boost_capy_tests PROPERTY COMPILE_WARNING_AS_ERROR ON) target_include_directories(boost_capy_tests PRIVATE . ../../) if(TARGET Boost::asio) diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp index 88b1f090..83125e4b 100644 --- a/test/unit/asio.cpp +++ b/test/unit/asio.cpp @@ -8,35 +8,17 @@ // - - -#include #if __has_include() -#include -#include -#include -#include +#include + #include #include #include #include +#include #include -#endif -#if __has_include() -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#endif #include #include @@ -44,140 +26,10 @@ #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 testFromExecutor() - { - ::asio::io_context ctx; - boost::capy::executor_from_standalone_asio exec{ctx.get_executor()}; - - bool done = false; - auto tsk = [&]() -> boost::capy::task - { - done = true; - co_return ; - }; - - boost::capy::run_async(exec)(tsk()); - BOOST_TEST(!done); - ctx.run(); - BOOST_TEST(done); - } - - - void testFromAnyIOExecutor() - { - ::asio::io_context ctx; - ::asio::any_io_executor any_exec{ctx.get_executor()}; - boost::capy::executor_from_standalone_asio exec{any_exec}; - - bool done = false; - auto tsk = [&]() -> boost::capy::task - { - done = true; - co_return ; - }; - - boost::capy::run_async(exec)(tsk()); - BOOST_TEST(!done); - ctx.run(); - BOOST_TEST(done); - } - - void testAsIoAwaitable() - { - bool done = false; - auto tsk = [&]() -> boost::capy::task - { - boost::capy::standalone_asio_executor_adapter wrapped_te{co_await capy::this_coro::executor}; - co_await ::asio::post(wrapped_te, as_io_awaitable); - done = true; - }; - int dispatch_count = 0; - int work_cnt = 0; - test_executor exec{0, dispatch_count, work_cnt}; - boost::capy::run_async(exec)(tsk()); - - BOOST_TEST(done); - } - - - void testAsioSpawn() - { - int dispatch_count = 0; - test_executor exec{0, dispatch_count}; - bool done = false; - auto tsk = [&]() -> - boost::capy::task - { - done = true; - co_return ; - }; - - auto ft = standalone_asio_spawn(exec, tsk(), ::asio::use_future); - - ft.get(); - BOOST_TEST(done); - BOOST_TEST(dispatch_count == 1); - } - - void run() - { - testExecutor(); - testFromExecutor(); - testFromAnyIOExecutor(); - testAsIoAwaitable(); - testAsioSpawn(); - } -}; - -#endif - -#if __has_include() - struct boost_asio_test { void testExecutor() @@ -216,7 +68,7 @@ struct boost_asio_test void testFromExecutor() { boost::asio::io_context ctx; - boost::capy::executor_from_asio exec{ctx.get_executor()}; + auto exec = wrap_asio_executor(ctx.get_executor()); bool done = false; auto tsk = [&]() -> boost::capy::task @@ -236,7 +88,8 @@ struct boost_asio_test { boost::asio::io_context ctx; boost::asio::any_io_executor any_exec{ctx.get_executor()}; - boost::capy::executor_from_asio exec{any_exec}; + auto exec = wrap_asio_executor(any_exec); + bool done = false; auto tsk = [&]() -> boost::capy::task @@ -299,19 +152,13 @@ struct boost_asio_test } }; -#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 + +#endif + diff --git a/test/unit/asio_both.cpp b/test/unit/asio_both.cpp new file mode 100644 index 00000000..915d2f05 --- /dev/null +++ b/test/unit/asio_both.cpp @@ -0,0 +1,111 @@ + +#if __has_include() && __has_include() + +#include +#include + + +#include +#include +#include + +#include +#include +#include + +#include "test_helpers.hpp" +#include "test_suite.hpp" + +namespace boost { +namespace capy { + + +struct boost_asio_both_test +{ + + boost::asio::awaitable aw_boost(executor_ref exec, task t) + { + int i = co_await asio_spawn(exec, std::move(t)); + BOOST_TEST(i == 42); + co_return i; + } + + + ::asio::awaitable aw_standalone(executor_ref exec, task t) + { + int i = co_await asio_spawn(exec, std::move(t)); + BOOST_TEST(i == 42); + co_return i; + } + + task foo() {co_return 42;} + + + void testBoost() + { + boost::asio::io_context ctx; + + int dispatch_count = 0; + test_executor te{dispatch_count}; + std::exception_ptr ep; + int i = 0; + + boost::asio::co_spawn( + ctx, + aw_boost(te, foo()), + [&](std::exception_ptr ep_, int i_) + { + ep = ep; + i = i_; + }); + + BOOST_TEST(i == 0); + ctx.run(); + + BOOST_TEST(i == 42); + BOOST_TEST(!ep); + BOOST_TEST(dispatch_count == 1); + } + + void testStandalone() + { + ::asio::io_context ctx; + + int dispatch_count = 0; + test_executor te{dispatch_count}; + std::exception_ptr ep; + int i = 0; + + ::asio::co_spawn( + ctx, + aw_standalone(te, foo()), + [&](std::exception_ptr ep_, int i_) + { + ep = ep; + i = i_; + }); + + BOOST_TEST(i == 0); + ctx.run(); + + BOOST_TEST(i == 42); + BOOST_TEST(!ep); + BOOST_TEST(dispatch_count == 1); + } + + + void run() + { + testBoost(); + testStandalone(); + } + +}; + +TEST_SUITE( + boost_asio_both_test, + "boost.capy.asio.both"); +#endif + +} // namespace capy +} // namespace boost diff --git a/test/unit/asio_standalone.cpp b/test/unit/asio_standalone.cpp new file mode 100644 index 00000000..58c45a3d --- /dev/null +++ b/test/unit/asio_standalone.cpp @@ -0,0 +1,162 @@ +// +// 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 +#include +#include + + +#include +#include +#include +#include "test_helpers.hpp" +#include "test_suite.hpp" + + +namespace boost { +namespace capy { + + +struct asio_standalone_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; + ::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 testFromExecutor() + { + ::asio::io_context ctx; + auto exec = wrap_asio_executor(ctx.get_executor()); + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + done = true; + co_return ; + }; + + boost::capy::run_async(exec)(tsk()); + BOOST_TEST(!done); + ctx.run(); + BOOST_TEST(done); + } + + + void testFromAnyIOExecutor() + { + ::asio::io_context ctx; + ::asio::any_io_executor any_exec{ctx.get_executor()}; + auto exec = wrap_asio_executor(any_exec); + + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + done = true; + co_return ; + }; + + boost::capy::run_async(exec)(tsk()); + BOOST_TEST(!done); + ctx.run(); + BOOST_TEST(done); + } + + void testAsIoAwaitable() + { + bool done = false; + auto tsk = [&]() -> boost::capy::task + { + boost::capy::asio_executor_adapter wrapped_te{co_await capy::this_coro::executor}; + co_await ::asio::post(wrapped_te, as_io_awaitable); + done = true; + }; + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::run_async(exec)(tsk()); + + BOOST_TEST(done); + } + + + void testAsioSpawn() + { + int dispatch_count = 0; + test_executor exec{0, dispatch_count}; + bool done = false; + auto tsk = [&]() -> + boost::capy::task + { + done = true; + co_return ; + }; + + auto ft = asio_spawn(exec, tsk(), ::asio::use_future); + + ft.get(); + BOOST_TEST(done); + BOOST_TEST(dispatch_count == 1); + } + + void run() + { + testExecutor(); + testFromExecutor(); + testFromAnyIOExecutor(); + testAsIoAwaitable(); + testAsioSpawn(); + } +}; + + +TEST_SUITE( + asio_standalone_test, + "boost.capy.asio.standalone"); + +} // namespace capy +} // namespace boost + +#endif + From 17161405a6ebb3e39775dcce701f78e673cdd863 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 15 Apr 2026 19:24:04 +0800 Subject: [PATCH 07/29] Embraced verticality --- include/boost/capy/asio/as_io_awaitable.hpp | 16 ++- include/boost/capy/asio/boost.hpp | 128 ++++++++++++------ .../capy/asio/detail/asio_context_service.hpp | 1 + .../detail/asio_coroutine_unique_handle.hpp | 1 + .../capy/asio/detail/completion_handler.hpp | 44 ++++-- .../capy/asio/detail/completion_traits.hpp | 2 +- .../boost/capy/asio/detail/continuation.hpp | 7 +- include/boost/capy/asio/detail/fwd.hpp | 1 + include/boost/capy/asio/executor_adapter.hpp | 37 +++-- .../boost/capy/asio/executor_from_asio.hpp | 33 +++-- include/boost/capy/asio/spawn.hpp | 3 +- include/boost/capy/asio/standalone.hpp | 125 +++++++++++------ 12 files changed, 267 insertions(+), 131 deletions(-) diff --git a/include/boost/capy/asio/as_io_awaitable.hpp b/include/boost/capy/asio/as_io_awaitable.hpp index 15ab56e0..99be89eb 100644 --- a/include/boost/capy/asio/as_io_awaitable.hpp +++ b/include/boost/capy/asio/as_io_awaitable.hpp @@ -38,13 +38,14 @@ struct as_io_awaitable_t /// Construct the adapted executor from the inner executor type. template - executor_with_default(const InnerExecutor1& ex, - typename std::enable_if< - std::conditional< - !std::is_same::value, - std::is_convertible, - std::false_type - >::type::value>::type = 0) noexcept + executor_with_default( + const InnerExecutor1& ex, + typename std::enable_if< + std::conditional< + !std::is_same::value, + std::is_convertible, + std::false_type + >::type::value>::type = 0) noexcept : InnerExecutor(ex) { } @@ -75,3 +76,4 @@ constexpr as_io_awaitable_t as_io_awaitable; } #endif + diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index f37bf18e..8015084e 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -42,7 +42,8 @@ boost::asio::execution_context& query(const asio_executor_adapter & exec, boost::asio::execution::context_t) noexcept { - using service = detail::asio_adapter_context_service; + using service = detail::asio_adapter_context_service< + boost::asio::execution_context>; return exec.context(). template use_service(); } @@ -54,9 +55,12 @@ constexpr boost::asio::execution::blocking_t { switch (Bits & exec.blocking_mask) { - case exec.blocking_never: return boost::asio::execution::blocking.never; - case exec.blocking_always: return boost::asio::execution::blocking.always; - case exec.blocking_possibly: return boost::asio::execution::blocking.possibly; + case exec.blocking_never: + return boost::asio::execution::blocking.never; + case exec.blocking_always: + return boost::asio::execution::blocking.always; + case exec.blocking_possibly: + return boost::asio::execution::blocking.possibly; default: return {}; } } @@ -66,8 +70,8 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::possibly_t) { - constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; - return asio_executor_adapter(exec); + constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; + return asio_executor_adapter(exec); } template @@ -75,8 +79,8 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::never_t) { - constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_never; - return asio_executor_adapter(exec); + constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_never; + return asio_executor_adapter(exec); } template @@ -84,8 +88,8 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::always_t) { - constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_always; - return asio_executor_adapter(exec); + constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_always; + return asio_executor_adapter(exec); } template @@ -105,7 +109,7 @@ static constexpr boost::asio::execution::outstanding_work_t query( template constexpr auto - require(const asio_executor_adapter & exec, + require(const asio_executor_adapter & exec, boost::asio::execution::outstanding_work_t::tracked_t) { constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; @@ -146,10 +150,14 @@ constexpr auto boost::asio::execution::allocator_t a) noexcept(std::is_nothrow_move_constructible_v) { - return asio_executor_adapter, Bits>( - exec, - exec.context().get_frame_allocator() - ); + return asio_executor_adapter< + Executor, + std::pmr::polymorphic_allocator, + Bits> + ( + exec, + exec.context().get_frame_allocator() + ); } namespace detail @@ -225,19 +233,25 @@ struct asio_boost_standard_executor execution_context& context() const noexcept { auto & ec = boost::asio::query(executor_, boost::asio::execution::context); - return boost::asio::use_service>(ec); + return boost::asio::use_service< + detail::asio_context_service + >(ec); } void on_work_started() const noexcept { auto & ec = boost::asio::query(executor_, boost::asio::execution::context); - boost::asio::use_service>(ec).work_started(executor_); + boost::asio::use_service< + detail::asio_work_tracker_service + >(ec).work_started(executor_); } void on_work_finished() const noexcept { auto & ec = boost::asio::query(executor_, boost::asio::execution::context); - boost::asio::use_service>(ec).work_finished(); + boost::asio::use_service< + detail::asio_work_tracker_service + >(ec).work_finished(); } @@ -257,14 +271,14 @@ struct asio_boost_standard_executor void post(continuation & c) const { boost::asio::prefer( - boost::asio::require(executor_, boost::asio::execution::blocking.never), - boost::asio::execution::relationship.fork, - boost::asio::execution::allocator( - std::pmr::polymorphic_allocator( - context().get_frame_allocator() - ) + boost::asio::require(executor_, boost::asio::execution::blocking.never), + boost::asio::execution::relationship.fork, + boost::asio::execution::allocator( + std::pmr::polymorphic_allocator( + context().get_frame_allocator() ) - ).execute(detail::asio_coroutine_unique_handle(c.h)); + ) + ).execute(detail::asio_coroutine_unique_handle(c.h)); } bool operator==(const asio_boost_standard_executor & rhs) const noexcept { @@ -280,15 +294,21 @@ struct asio_boost_standard_executor }; -template> Token> +template + > Token> auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) { return asio_spawn(exec, std::forward(runnable))(std::move(token)); } -template> Token> +template + > Token> auto asio_spawn(Context & ctx, Runnable && runnable, Token token) { return asio_spawn(ctx.get_executor(), std::forward(runnable))(std::move(token)); @@ -298,7 +318,10 @@ auto asio_spawn(Context & ctx, Runnable && runnable, Token token) template struct boost::asio::async_result - : boost::capy::detail::async_result_impl + : boost::capy::detail::async_result_impl< + boost::asio::cancellation_signal, + boost::asio::cancellation_type, + Ts...> { }; @@ -318,7 +341,8 @@ struct boost_asio_promise_type_allocator_base Handler & handler, Ex & exec, Runnable & r) { - using allocator_type = std::allocator_traits::template rebind_alloc; + using allocator_type = std::allocator_traits + ::template rebind_alloc; allocator_type allocator(boost::asio::get_associated_allocator(handler)); using traits = std::allocator_traits; //::rebind_alloc() @@ -339,8 +363,10 @@ struct boost_asio_promise_type_allocator_base if (const auto d = n % sizeof(std::max_align_t); d >= 0u) n += (sizeof(std::max_align_t) - d); - using allocator_type = std::allocator_traits::template rebind_alloc; - auto allocator_p = reinterpret_cast(static_cast(ptr) + n); + using allocator_type = std::allocator_traits + ::template rebind_alloc; + auto allocator_p = reinterpret_cast( + static_cast(ptr) + n); auto allocator = std::move(*allocator_p); allocator_p->~allocator_type(); @@ -351,11 +377,16 @@ struct boost_asio_promise_type_allocator_base template struct boost_asio_init_promise_type - : boost_asio_promise_type_allocator_base> + : boost_asio_promise_type_allocator_base< + boost::asio::associated_allocator_t> { using args_type = completion_tuple_for_io_runnable; - boost_asio_init_promise_type(boost_asio_init &, Handler & h, Ex & exec, Runnable & r) + boost_asio_init_promise_type( + boost_asio_init &, + Handler & h, + Ex & exec, + Runnable & r) : handler(h), ex(exec) {} Handler & handler; @@ -392,7 +423,8 @@ struct boost_asio_init_promise_type args_); asio_executor_adapter aex(ex); - auto exec = boost::asio::get_associated_immediate_executor(handler, ex_); + auto exec = + boost::asio::get_associated_immediate_executor(handler, ex_); boost::asio::dispatch(exec, std::move(handler)); } void await_resume() const {} @@ -415,7 +447,8 @@ struct boost_asio_init_promise_type bool await_ready() {return r.await_ready(); } - std::coroutine_handle<> await_suspend(std::coroutine_handle tr) + std::coroutine_handle<> await_suspend( + std::coroutine_handle tr) { // always post in auto h = r.handle(); @@ -424,7 +457,9 @@ struct boost_asio_init_promise_type env.executor = ex; env.stop_token = stop_src.get_token(); - cancel_slot = boost::asio::get_associated_cancellation_slot(tr.promise().handler); + cancel_slot = + boost::asio::get_associated_cancellation_slot(tr.promise().handler); + if (cancel_slot.is_connected()) cancel_slot.assign( [this](boost::asio::cancellation_type ct) @@ -505,7 +540,10 @@ struct boost_asio_init template requires - boost::asio::completion_token_for> + boost::asio::completion_token_for< + Token, + completion_signature_for_io_runnable + > struct initialize_asio_spawn_helper { template @@ -524,9 +562,17 @@ struct initialize_asio_spawn_helper template -struct std::coroutine_traits +struct std::coroutine_traits { - using promise_type = boost::capy::detail::boost_asio_init_promise_type; + using promise_type + = boost::capy::detail::boost_asio_init_promise_type< + Handler, + Executor, + Runnable>; }; #endif //BOOST_CAPY_ASIO_BOOST_HPP diff --git a/include/boost/capy/asio/detail/asio_context_service.hpp b/include/boost/capy/asio/detail/asio_context_service.hpp index f1ac3e8c..271231b0 100644 --- a/include/boost/capy/asio/detail/asio_context_service.hpp +++ b/include/boost/capy/asio/detail/asio_context_service.hpp @@ -35,3 +35,4 @@ 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 index d2da7ce9..f198b00d 100644 --- a/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp +++ b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp @@ -47,3 +47,4 @@ struct asio_coroutine_unique_handle } #endif + diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index db91b50d..d2ab43cb 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -41,7 +41,7 @@ struct asio_immediate_executor_helper ((*completed_immediately == initiating) || (*completed_immediately == maybe))) { - // only use this indicator if the fn will actually call our completion-handler + // only use this indicator if the fn will actually call our handler // otherwise this was a single op in a composed operation *completed_immediately = maybe; fn(); @@ -70,9 +70,13 @@ struct asio_immediate_executor_helper 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) + 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) { } }; @@ -85,7 +89,9 @@ struct asio_coroutine_completion_handler std::optional> & result; const capy::io_env * env; CancellationSlot slot; - asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; + using completed_immediately_t = asio_immediate_executor_helper::completed_immediately_t; + + completed_immediately_t * completed_immediately = nullptr; using allocator_type = std::pmr::polymorphic_allocator; allocator_type get_allocator() const {return env->frame_allocator;} @@ -107,8 +113,12 @@ struct asio_coroutine_completion_handler std::optional> & result, const capy::io_env * env, CancellationSlot slot = {}, - asio_immediate_executor_helper::completed_immediately_t * ci = nullptr) - : handle(h), result(result), env(env), slot(slot), completed_immediately(ci) {} + completed_immediately_t * ci = nullptr) + : handle(h) + , result(result) + , env(env) + , slot(slot), completed_immediately(ci) + {} asio_coroutine_completion_handler( asio_coroutine_completion_handler && @@ -129,10 +139,12 @@ struct async_result_impl template struct awaitable_t { - using completed_immediately_t = capy::detail::asio_immediate_executor_helper::completed_immediately_t; + using completed_immediately_t + = asio_immediate_executor_helper::completed_immediately_t; CancellationSignal signal; completed_immediately_t completed_immediately; + struct cb { CancellationSignal &signal; @@ -145,7 +157,7 @@ struct async_result_impl bool await_suspend(std::coroutine_handle<> h, const capy::io_env * env) { - completed_immediately = capy::detail::asio_immediate_executor_helper::completed_immediately_t::initiating; + completed_immediately = completed_immediately_t::initiating; stopper.emplace(env->stop_token, signal); using slot_t = decltype(CancellationSignal().slot()); capy::detail::asio_coroutine_completion_handler ch( @@ -171,18 +183,23 @@ struct async_result_impl awaitable_t(Initiation init, std::tuple args) - : init_(std::move(init)), args_(std::move(args)) {} + : init_(std::move(init)), args_(std::move(args)) + { + } + awaitable_t(awaitable_t && rhs) noexcept - : init_(std::move(rhs.init_)), args_(std::move(rhs.args_)), result_(std::move(rhs.result_)) {} + : init_(std::move(rhs.init_)) + , args_(std::move(rhs.args_)) + , result_(std::move(rhs.result_)) {} private: Initiation init_; std::tuple args_; std::optional> result_; }; - template + template static auto initiate(Initiation&& initiation, - RawCompletionToken&&, Args&&... args) + RawToken&&, Args&&... args) { return awaitable_t< std::decay_t, @@ -196,3 +213,4 @@ struct async_result_impl } #endif //BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER + diff --git a/include/boost/capy/asio/detail/completion_traits.hpp b/include/boost/capy/asio/detail/completion_traits.hpp index 3510dc66..b98d428b 100644 --- a/include/boost/capy/asio/detail/completion_traits.hpp +++ b/include/boost/capy/asio/detail/completion_traits.hpp @@ -14,7 +14,6 @@ #include #include -#include namespace boost { namespace capy { @@ -70,3 +69,4 @@ using completion_tuple_for_io_runnable } #endif + diff --git a/include/boost/capy/asio/detail/continuation.hpp b/include/boost/capy/asio/detail/continuation.hpp index 556ba792..d00ee2ca 100644 --- a/include/boost/capy/asio/detail/continuation.hpp +++ b/include/boost/capy/asio/detail/continuation.hpp @@ -63,7 +63,8 @@ struct continuation_handle_promise_base_> }; template -struct continuation_handle_promise_type : continuation_handle_promise_base_ +struct continuation_handle_promise_type + : continuation_handle_promise_base_ { std::suspend_always initial_suspend() const noexcept {return {};} std::suspend_never final_suspend() const noexcept {return {};} @@ -109,7 +110,9 @@ struct continuation_helper }; template Function, typename Allocator> -continuation_helper make_continuation_helper(Function func, Allocator alloc) +continuation_helper make_continuation_helper( + Function func, + Allocator alloc) { co_yield func; } diff --git a/include/boost/capy/asio/detail/fwd.hpp b/include/boost/capy/asio/detail/fwd.hpp index 73647b6e..e778a8f0 100644 --- a/include/boost/capy/asio/detail/fwd.hpp +++ b/include/boost/capy/asio/detail/fwd.hpp @@ -50,3 +50,4 @@ struct query_result; #endif // BOOST_CAPY_ASIO_DETAIL_FWD_HPP + diff --git a/include/boost/capy/asio/executor_adapter.hpp b/include/boost/capy/asio/executor_adapter.hpp index ea179e79..354c3c43 100644 --- a/include/boost/capy/asio/executor_adapter.hpp +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -49,18 +49,21 @@ 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_) + asio_executor_adapter( + const asio_executor_adapter & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_), allocator_(rhs.allocator_) { if constexpr((Bits & work_mask) == 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_)) + 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((Bits & work_mask) == work_tracked) executor_.on_work_started(); @@ -81,15 +84,17 @@ struct asio_executor_adapter const Allocator & alloc) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_copy_constructible_v) - : executor_(std::move(executor.executor_)), allocator_(alloc) + : executor_(std::move(executor.executor_)), allocator_(alloc) { if constexpr((Bits & work_mask) == 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()) + asio_executor_adapter(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + , allocator_(executor_.context().get_frame_allocator()) { if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_started(); @@ -102,7 +107,8 @@ struct asio_executor_adapter } template - asio_executor_adapter & operator=(const asio_executor_adapter & rhs) + asio_executor_adapter & operator=( + const asio_executor_adapter & rhs) { if constexpr((Bits & work_mask) == work_tracked) @@ -131,9 +137,12 @@ struct asio_executor_adapter void execute(Function&& f) const { if constexpr ((Bits & blocking_mask) == blocking_never) - executor_.post(detail::make_continuation(std::forward(f), allocator_)); + executor_.post( + detail::make_continuation(std::forward(f), allocator_)); else if constexpr((Bits & blocking_mask) == blocking_possibly) - executor_.dispatch(detail::make_continuation(std::forward(f), allocator_)).resume(); + executor_.dispatch( + detail::make_continuation(std::forward(f), allocator_) + ).resume(); else if constexpr((Bits & blocking_mask) == blocking_always) std::forward(f)(); } @@ -151,11 +160,9 @@ struct asio_executor_adapter }; - - - } } #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 index 17108ffd..56dddafb 100644 --- a/include/boost/capy/asio/executor_from_asio.hpp +++ b/include/boost/capy/asio/executor_from_asio.hpp @@ -37,16 +37,18 @@ concept AsioNetTsExecutor = requires (Executor exec, } ; template -concept AsioBoostStandardExecutor = std::same_as>::type, - boost::asio::execution_context&>; +concept AsioBoostStandardExecutor = std::same_as< + typename boost::asio::query_result< + Executor, + boost::asio::execution::detail::context_t<0>>::type, + boost::asio::execution_context&>; template -concept AsioStandaloneStandardExecutor = std::same_as>::type, - ::asio::execution_context&>; +concept AsioStandaloneStandardExecutor = std::same_as< + typename ::asio::query_result< + Executor, + ::asio::execution::detail::context_t<0>>::type, + ::asio::execution_context&>; } @@ -133,18 +135,25 @@ auto wrap_asio_executor(Executor && exec) { using executor_t = std::decay_t; if constexpr (detail::AsioNetTsExecutor) - return asio_net_ts_executor(std::forward(exec)); + return asio_net_ts_executor( + std::forward(exec) + ); else if constexpr (detail::AsioBoostStandardExecutor) - return asio_boost_standard_executor(std::forward(exec)); + return asio_boost_standard_executor( + std::forward(exec) + ); else if constexpr (detail::AsioStandaloneStandardExecutor) - return asio_standalone_standard_executor(std::forward(exec)); + return asio_standalone_standard_executor( + std::forward(exec) + ); else static_assert(sizeof(Executor) == 0, "Unknown executor type"); }; template -using wrap_asio_executor_t = decltype(wrap_asio_executor(std::declval())); +using wrap_asio_executor_t + = decltype(wrap_asio_executor(std::declval())); diff --git a/include/boost/capy/asio/spawn.hpp b/include/boost/capy/asio/spawn.hpp index 082f8bb1..89651110 100644 --- a/include/boost/capy/asio/spawn.hpp +++ b/include/boost/capy/asio/spawn.hpp @@ -71,7 +71,8 @@ struct asio_spawn_op template Token> auto operator()(Token && token) { - return detail::initialize_asio_standalone_spawn_helper::init( + return detail::initialize_asio_standalone_spawn_helper::init + ( std::move(executor_), std::move(runnable_), std::forward(token) diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 1c547c1c..b3c828bf 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -42,7 +42,8 @@ ::asio::execution_context& query(const asio_executor_adapter & exec, ::asio::execution::context_t) noexcept { - using service = detail::asio_adapter_context_service<::asio::execution_context>; + using service = detail::asio_adapter_context_service< + ::asio::execution_context>; return exec.context(). template use_service(); } @@ -54,9 +55,9 @@ constexpr ::asio::execution::blocking_t { switch (Bits & exec.blocking_mask) { - case exec.blocking_never: return ::asio::execution::blocking.never; - case exec.blocking_always: return ::asio::execution::blocking.always; - case exec.blocking_possibly: return ::asio::execution::blocking.possibly; + case exec.blocking_never: return ::asio::execution::blocking.never; + case exec.blocking_always: return ::asio::execution::blocking.always; + case exec.blocking_possibly: return ::asio::execution::blocking.possibly; default: return {}; } } @@ -66,17 +67,17 @@ constexpr auto require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::possibly_t) { - constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; - return asio_executor_adapter(exec); + constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; + return asio_executor_adapter(exec); } template constexpr auto - require(const asio_executor_adapter & exec, + require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::never_t) { - constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_never; - return asio_executor_adapter(exec); + constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_never; + return asio_executor_adapter(exec); } template @@ -122,20 +123,20 @@ constexpr auto } -template +template constexpr Allocator query( const asio_executor_adapter & exec, - ::asio::execution::allocator_t) noexcept + ::asio::execution::allocator_t) noexcept { return exec.get_allocator(); } -template +template constexpr auto require(const asio_executor_adapter & exec, - ::asio::execution::allocator_t a) + ::asio::execution::allocator_t a) { - return asio_executor_adapter( + return asio_executor_adapter( exec, a.value() ); } @@ -146,7 +147,11 @@ constexpr auto ::asio::execution::allocator_t a) noexcept(std::is_nothrow_move_constructible_v) { - return asio_executor_adapter, Bits>( + return asio_executor_adapter< + Executor, + std::pmr::polymorphic_allocator, + Bits> + ( exec, exec.context().get_frame_allocator() ); @@ -156,7 +161,8 @@ namespace detail { template -struct asio_standalone_work_tracker_service : ::asio::execution_context::service +struct asio_standalone_work_tracker_service : + ::asio::execution_context::service { static ::asio::execution_context::id id; @@ -197,7 +203,8 @@ struct asio_standalone_work_tracker_service : ::asio::execution_context::service template -::asio::execution_context::id asio_standalone_work_tracker_service::id; +::asio::execution_context::id + asio_standalone_work_tracker_service::id; } @@ -211,12 +218,13 @@ struct asio_standalone_standard_executor : executor_(std::move(executor)) { } - asio_standalone_standard_executor(asio_standalone_standard_executor && rhs) + asio_standalone_standard_executor(asio_standalone_standard_executor && rhs) noexcept(std::is_nothrow_move_constructible_v) : executor_(std::move(rhs.executor_)) { } - asio_standalone_standard_executor(const asio_standalone_standard_executor & rhs) + asio_standalone_standard_executor( + const asio_standalone_standard_executor & rhs) noexcept(std::is_nothrow_copy_constructible_v) : executor_(rhs.executor_) { @@ -225,19 +233,25 @@ struct asio_standalone_standard_executor execution_context& context() const noexcept { auto & ec = ::asio::query(executor_, ::asio::execution::context); - return ::asio::use_service>(ec); + return ::asio::use_service< + detail::asio_context_service<::asio::execution_context> + >(ec); } void on_work_started() const noexcept { auto & ec = ::asio::query(executor_, ::asio::execution::context); - ::asio::use_service>(ec).work_started(executor_); + ::asio::use_service< + detail::asio_standalone_work_tracker_service + >(ec).work_started(executor_); } void on_work_finished() const noexcept { auto & ec = ::asio::query(executor_, ::asio::execution::context); - ::asio::use_service>(ec).work_finished(); + ::asio::use_service< + detail::asio_standalone_work_tracker_service + >(ec).work_finished(); } @@ -266,11 +280,13 @@ struct asio_standalone_standard_executor ) ).execute(detail::asio_coroutine_unique_handle(c.h)); } - bool operator==(const asio_standalone_standard_executor & rhs) const noexcept + bool operator==( + const asio_standalone_standard_executor & rhs) const noexcept { return executor_ == rhs.executor_; } - bool operator!=(const asio_standalone_standard_executor & rhs) const noexcept + bool operator!=( + const asio_standalone_standard_executor & rhs) const noexcept { return executor_ != rhs.executor_; } @@ -280,25 +296,38 @@ struct asio_standalone_standard_executor }; -template> Token> +template + > Token> auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) { return asio_spawn(exec, std::forward(runnable))(std::move(token)); } -template> Token> +template + > Token + > auto asio_spawn(Context & ctx, Runnable && runnable, Token token) { - return asio_spawn(ctx.get_executor(), std::forward(runnable))(std::move(token)); + return asio_spawn(ctx.get_executor(), std::forward(runnable)) + (std::move(token)); } } template struct asio::async_result - : boost::capy::detail::async_result_impl<::asio::cancellation_signal, ::asio::cancellation_type, Ts...> + : boost::capy::detail::async_result_impl + < + ::asio::cancellation_signal, + ::asio::cancellation_type, + Ts... + > { }; @@ -318,9 +347,10 @@ struct boost_asio_standalone_promise_type_allocator_base Handler & handler, Ex & exec, Runnable & r) { - using allocator_type = std::allocator_traits::template rebind_alloc; + using allocator_type = std::allocator_traits + ::template rebind_alloc; allocator_type allocator(::asio::get_associated_allocator(handler)); - using traits = std::allocator_traits; //::rebind_alloc() + using traits = std::allocator_traits; // round n up to max_align if (const auto d = n % sizeof(std::max_align_t); d >= 0u) @@ -339,8 +369,10 @@ struct boost_asio_standalone_promise_type_allocator_base if (const auto d = n % sizeof(std::max_align_t); d >= 0u) n += (sizeof(std::max_align_t) - d); - using allocator_type = std::allocator_traits::template rebind_alloc; - auto allocator_p = reinterpret_cast(static_cast(ptr) + n); + using allocator_type = std::allocator_traits + ::template rebind_alloc; + auto allocator_p = reinterpret_cast( + static_cast(ptr) + n); auto allocator = std::move(*allocator_p); allocator_p->~allocator_type(); @@ -351,7 +383,9 @@ struct boost_asio_standalone_promise_type_allocator_base template struct boost_asio_standalone_init_promise_type - : boost_asio_standalone_promise_type_allocator_base<::asio::associated_allocator_t> + : boost_asio_standalone_promise_type_allocator_base< + ::asio::associated_allocator_t + > { using args_type = completion_tuple_for_io_runnable; @@ -429,7 +463,8 @@ struct boost_asio_standalone_init_promise_type env.executor = ex; env.stop_token = stop_src.get_token(); - cancel_slot = ::asio::get_associated_cancellation_slot(tr.promise().handler); + cancel_slot = + ::asio::get_associated_cancellation_slot(tr.promise().handler); if (cancel_slot.is_connected()) cancel_slot.assign( [this](::asio::cancellation_type ct) @@ -510,7 +545,10 @@ struct boost_asio_standalone_init template requires - ::asio::completion_token_for> + ::asio::completion_token_for< + Token, + completion_signature_for_io_runnable + > struct initialize_asio_standalone_spawn_helper { template @@ -529,9 +567,18 @@ struct initialize_asio_standalone_spawn_helper template -struct std::coroutine_traits +struct std::coroutine_traits< + void, + boost::capy::detail::boost_asio_standalone_init&, + Handler, + Executor, + Runnable> { - using promise_type = boost::capy::detail::boost_asio_standalone_init_promise_type; + using promise_type + = boost::capy::detail::boost_asio_standalone_init_promise_type< + Handler, + Executor, + Runnable>; }; #endif // BOOST_CAPY_ASIO_STANDALONE_HPP From fd00f812f1ae5f0fe4a02ca4ba72311d6bf15749 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 15 Apr 2026 22:11:05 +0800 Subject: [PATCH 08/29] Asio type concepts differentiate correctly --- include/boost/capy/asio/boost.hpp | 10 +++- include/boost/capy/asio/standalone.hpp | 8 ++- test/unit/asio_both.cpp | 76 ++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 8015084e..b33442ea 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -539,7 +539,7 @@ struct boost_asio_init }; template - requires + requires boost::asio::completion_token_for< Token, completion_signature_for_io_runnable @@ -547,7 +547,13 @@ template struct initialize_asio_spawn_helper { template - static auto init(Executor ex, Runnable r, Token && tk) + static auto init(Executor ex, Runnable r, Token && tk) + -> decltype( boost::asio::async_initiate< + Token, + completion_signature_for_io_runnable>( + boost_asio_init{}, + tk, std::move(ex), std::move(r) + )) { return boost::asio::async_initiate< Token, diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index b3c828bf..9cd13b10 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -552,7 +552,13 @@ template struct initialize_asio_standalone_spawn_helper { template - static auto init(Executor ex, Runnable r, Token && tk) + static auto init(Executor ex, Runnable r, Token && tk) + -> decltype(::asio::async_initiate< + Token, + completion_signature_for_io_runnable>( + boost_asio_standalone_init{}, + tk, std::move(ex), std::move(r) + )) { return ::asio::async_initiate< Token, diff --git a/test/unit/asio_both.cpp b/test/unit/asio_both.cpp index 915d2f05..3ac52689 100644 --- a/test/unit/asio_both.cpp +++ b/test/unit/asio_both.cpp @@ -5,10 +5,12 @@ #include +#include #include #include #include +#include #include #include #include @@ -38,7 +40,24 @@ struct boost_asio_both_test co_return i; } + + boost::asio::awaitable aw_boost_tuple(executor_ref exec, task t) + { + auto [ep, i] = co_await asio_spawn(exec, std::move(t))(boost::asio::as_tuple); + BOOST_TEST(i == 42); + co_return i; + } + + + ::asio::awaitable aw_standalone_tuple(executor_ref exec, task t) + { + auto [ep, i] = co_await asio_spawn(exec, std::move(t))(::asio::as_tuple); + BOOST_TEST(i == 42); + co_return i; + } + task foo() {co_return 42;} + task bar() {throw 42; co_return 0;} void testBoost() @@ -67,6 +86,33 @@ struct boost_asio_both_test BOOST_TEST(dispatch_count == 1); } + + void testBoostTuple() + { + boost::asio::io_context ctx; + + int dispatch_count = 0; + test_executor te{dispatch_count}; + std::exception_ptr ep; + int i = 0; + + boost::asio::co_spawn( + ctx, + aw_boost_tuple(te, foo()), + [&](std::exception_ptr ep_, int i_) + { + ep = ep; + i = i_; + }); + + BOOST_TEST(i == 0); + ctx.run(); + + BOOST_TEST(i == 42); + BOOST_TEST(!ep); + BOOST_TEST(dispatch_count == 1); + } + void testStandalone() { ::asio::io_context ctx; @@ -94,10 +140,40 @@ struct boost_asio_both_test } + void testStandaloneTuple() + { + ::asio::io_context ctx; + + int dispatch_count = 0; + test_executor te{dispatch_count}; + std::exception_ptr ep; + int i = 0; + + ::asio::co_spawn( + ctx, + aw_standalone_tuple(te, foo()), + [&](std::exception_ptr ep_, int i_) + { + ep = ep; + i = i_; + }); + + BOOST_TEST(i == 0); + ctx.run(); + + BOOST_TEST(i == 42); + BOOST_TEST(!ep); + BOOST_TEST(dispatch_count == 1); + } + void run() { testBoost(); + testBoostTuple(); + testStandalone(); + + testStandaloneTuple(); } }; From fdb7ce4f3afd6b025672c187bbdb80fdd9c39a4b Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 05:57:18 +0800 Subject: [PATCH 09/29] Removed accidental commits --- include/boost/capy/ex/run_async.hpp | 12 +----------- test/unit/CMakeLists.txt | 6 ------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/include/boost/capy/ex/run_async.hpp b/include/boost/capy/ex/run_async.hpp index 2b142ef3..6f1b061c 100644 --- a/include/boost/capy/ex/run_async.hpp +++ b/include/boost/capy/ex/run_async.hpp @@ -419,16 +419,6 @@ class [[nodiscard]] run_async_wrapper p.wg_.executor().dispatch(p.task_cont_).resume(); } }; -/* - -template -concept value_handler = std::invocable || - std::invocable().await_resume())> - -template -concept error_handler = std::invocable; - -*/ // Executor only (uses default recycling allocator) @@ -501,7 +491,7 @@ run_async(Ex ex) @see task @see executor */ -template +template [[nodiscard]] auto run_async(Ex ex, H1 h1) { diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 24b36205..5a0c31c8 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -12,11 +12,6 @@ list(APPEND PFILES CMakeLists.txt Jamfile) -# TODO REMOVE -include_directories(/home/klemens/develop/asio/include /home/klemens/develop/boost) - - - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) @@ -26,7 +21,6 @@ target_link_libraries( Boost::capy_test_suite_main Boost::capy) -set_property(TARGET boost_capy_tests PROPERTY COMPILE_WARNING_AS_ERROR ON) target_include_directories(boost_capy_tests PRIVATE . ../../) if(TARGET Boost::asio) From 9561d6ba42de9fe87a97c971dd8e7947714a3336 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 06:22:39 +0800 Subject: [PATCH 10/29] Compiling doesn't generate warnings --- include/boost/capy/asio/boost.hpp | 18 +++++++++++------- .../boost/capy/asio/detail/continuation.hpp | 2 +- include/boost/capy/asio/executor_adapter.hpp | 2 +- include/boost/capy/asio/standalone.hpp | 18 +++++++++++------- test/unit/asio_both.cpp | 8 ++++---- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index b33442ea..3136d3a1 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -339,15 +339,14 @@ struct boost_asio_promise_type_allocator_base template void * operator new (std::size_t n, boost_asio_init &, Handler & handler, - Ex & exec, Runnable & r) + Ex &, Runnable &) { using allocator_type = std::allocator_traits ::template rebind_alloc; allocator_type allocator(boost::asio::get_associated_allocator(handler)); - using traits = std::allocator_traits; //::rebind_alloc() // round n up to max_align - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + if (const auto d = n % sizeof(std::max_align_t); d > 0u) n += (sizeof(std::max_align_t) - d); auto mem = std::allocator_traits:: @@ -360,7 +359,7 @@ struct boost_asio_promise_type_allocator_base } void operator delete(void * ptr, std::size_t n) { - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + if (const auto d = n % sizeof(std::max_align_t); d > 0u) n += (sizeof(std::max_align_t) - d); using allocator_type = std::allocator_traits @@ -386,7 +385,7 @@ struct boost_asio_init_promise_type boost_asio_init &, Handler & h, Ex & exec, - Runnable & r) + Runnable &) : handler(h), ex(exec) {} Handler & handler; @@ -445,6 +444,11 @@ struct boost_asio_init_promise_type continuation c; + wrapper(Runnable && r, const Ex &ex) + : r(std::move(r)), ex(ex) + { + } + bool await_ready() {return r.await_ready(); } std::coroutine_handle<> await_suspend( @@ -529,8 +533,8 @@ struct boost_asio_init { template void operator()( - Handler h, - Ex executor, + Handler , + Ex, Runnable runnable) { auto res = co_await runnable; diff --git a/include/boost/capy/asio/detail/continuation.hpp b/include/boost/capy/asio/detail/continuation.hpp index d00ee2ca..79e78698 100644 --- a/include/boost/capy/asio/detail/continuation.hpp +++ b/include/boost/capy/asio/detail/continuation.hpp @@ -112,7 +112,7 @@ struct continuation_helper template Function, typename Allocator> continuation_helper make_continuation_helper( Function func, - Allocator alloc) + Allocator) { co_yield func; } diff --git a/include/boost/capy/asio/executor_adapter.hpp b/include/boost/capy/asio/executor_adapter.hpp index 354c3c43..f8efc096 100644 --- a/include/boost/capy/asio/executor_adapter.hpp +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -27,7 +27,7 @@ struct asio_adapter_context_service // shutdown is protected ExecutionContext { - asio_adapter_context_service(boost::capy::execution_context & ctx) {} + asio_adapter_context_service(boost::capy::execution_context &) {} void shutdown() override {ExecutionContext::shutdown();} }; diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 9cd13b10..50e8cb81 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -345,15 +345,14 @@ struct boost_asio_standalone_promise_type_allocator_base template void * operator new (std::size_t n, boost_asio_standalone_init &, Handler & handler, - Ex & exec, Runnable & r) + Ex &, Runnable &) { using allocator_type = std::allocator_traits ::template rebind_alloc; allocator_type allocator(::asio::get_associated_allocator(handler)); - using traits = std::allocator_traits; // round n up to max_align - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + if (const auto d = n % sizeof(std::max_align_t); d > 0u) n += (sizeof(std::max_align_t) - d); auto mem = std::allocator_traits:: @@ -366,7 +365,7 @@ struct boost_asio_standalone_promise_type_allocator_base } void operator delete(void * ptr, std::size_t n) { - if (const auto d = n % sizeof(std::max_align_t); d >= 0u) + if (const auto d = n % sizeof(std::max_align_t); d > 0u) n += (sizeof(std::max_align_t) - d); using allocator_type = std::allocator_traits @@ -393,7 +392,7 @@ struct boost_asio_standalone_init_promise_type boost_asio_standalone_init &, Handler & h, Ex & exec, - Runnable & r) + Runnable &) : handler(h), ex(exec) {} Handler & handler; @@ -449,6 +448,11 @@ struct boost_asio_standalone_init_promise_type std::stop_source stop_src; ::asio::cancellation_slot cancel_slot; + wrapper(Runnable && r, const Ex &ex) + : r(std::move(r)), ex(ex) + { + } + continuation c; bool await_ready() {return r.await_ready(); } @@ -534,8 +538,8 @@ struct boost_asio_standalone_init { template void operator()( - Handler h, - Ex executor, + Handler , + Ex, Runnable runnable) { auto res = co_await runnable; diff --git a/test/unit/asio_both.cpp b/test/unit/asio_both.cpp index 3ac52689..76ceee27 100644 --- a/test/unit/asio_both.cpp +++ b/test/unit/asio_both.cpp @@ -74,7 +74,7 @@ struct boost_asio_both_test aw_boost(te, foo()), [&](std::exception_ptr ep_, int i_) { - ep = ep; + ep = ep_; i = i_; }); @@ -101,7 +101,7 @@ struct boost_asio_both_test aw_boost_tuple(te, foo()), [&](std::exception_ptr ep_, int i_) { - ep = ep; + ep = ep_; i = i_; }); @@ -127,7 +127,7 @@ struct boost_asio_both_test aw_standalone(te, foo()), [&](std::exception_ptr ep_, int i_) { - ep = ep; + ep = ep_; i = i_; }); @@ -154,7 +154,7 @@ struct boost_asio_both_test aw_standalone_tuple(te, foo()), [&](std::exception_ptr ep_, int i_) { - ep = ep; + ep = ep_; i = i_; }); From bb063293058fd26080cec305ce85d64305548693 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 06:43:02 +0800 Subject: [PATCH 11/29] Fixed #if in test --- test/unit/asio_both.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unit/asio_both.cpp b/test/unit/asio_both.cpp index 76ceee27..3c942e72 100644 --- a/test/unit/asio_both.cpp +++ b/test/unit/asio_both.cpp @@ -181,7 +181,10 @@ struct boost_asio_both_test TEST_SUITE( boost_asio_both_test, "boost.capy.asio.both"); -#endif } // namespace capy } // namespace boost + +#endif + + From fca3caf2a28ac3ecfb48c5e12c469575688d3a4e Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 07:13:42 +0800 Subject: [PATCH 12/29] [claude] Asio integration is documented --- doc/modules/ROOT/nav.adoc | 1 + .../4.coroutines/4i.asio-integration.adoc | 416 ++++++++++++++++++ include/boost/capy/asio/as_io_awaitable.hpp | 110 ++++- include/boost/capy/asio/boost.hpp | 305 ++++++++++--- include/boost/capy/asio/executor_adapter.hpp | 223 ++++++++-- .../boost/capy/asio/executor_from_asio.hpp | 175 +++++++- include/boost/capy/asio/spawn.hpp | 143 +++++- include/boost/capy/asio/standalone.hpp | 240 +++++++--- 8 files changed, 1418 insertions(+), 195 deletions(-) create mode 100644 doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 6f070d25..fe51d940 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -20,6 +20,7 @@ ** xref:4.coroutines/4f.composition.adoc[Concurrent Composition] ** xref:4.coroutines/4g.allocators.adoc[Frame Allocators] ** xref:4.coroutines/4h.lambda-captures.adoc[Lambda Coroutine Captures] +** xref:4.coroutines/4i.asio-integration.adoc[Asio Integration] * xref:5.buffers/5.intro.adoc[Buffer Sequences] ** xref:5.buffers/5a.overview.adoc[Why Concepts, Not Spans] ** xref:5.buffers/5b.types.adoc[Buffer Types] diff --git a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc new file mode 100644 index 00000000..df8c9c99 --- /dev/null +++ b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc @@ -0,0 +1,416 @@ +// +// 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 +// + += Asio Integration + +Capy provides seamless integration with both Boost.Asio and standalone Asio. This chapter explains how to use Asio's I/O facilities within capy coroutines and how to spawn capy coroutines from Asio code. + +== Prerequisites + +* Completed xref:4.coroutines/4a.tasks.adoc[The task Type] +* Completed xref:4.coroutines/4b.launching.adoc[Launching Coroutines] +* Familiarity with Asio's async model and completion tokens + +== Overview + +Capy's Asio integration provides two directions of interoperability: + +1. **Capy to Asio**: Await Asio async operations inside capy coroutines using `as_io_awaitable` +2. **Asio to Capy**: Spawn capy coroutines from Asio code using `asio_spawn` + +Additionally, you can: + +* Wrap Asio executors for use with capy (`wrap_asio_executor`) +* Wrap capy executors for use with Asio (`asio_executor_adapter`) + +== Choosing the Right Header + +[cols="1,2"] +|=== +| Header | Use When + +| `` +| Using Boost.Asio (`boost::asio` namespace) + +| `` +| Using standalone Asio (`asio` namespace) +|=== + +Both headers provide identical functionality, just targeting different Asio variants. + +== Awaiting Asio Operations in Capy Coroutines + +The `as_io_awaitable` completion token allows you to `co_await` any Asio async operation directly within a capy `io_task`. + +=== Basic Usage + +[source,cpp] +---- +#include +#include + +namespace capy = boost::capy; +namespace asio = boost::asio; + +capy::io_task read_some(asio::ip::tcp::socket& socket) +{ + std::array buffer; + + // Await the Asio operation using as_io_awaitable + auto [ec, bytes_read] = co_await socket.async_read_some( + asio::buffer(buffer), + capy::as_io_awaitable); + + if (ec) + throw std::system_error(ec); + + co_return bytes_read; +} +---- + +The `as_io_awaitable` token transforms the operation into an awaitable that: + +* Suspends the coroutine until the operation completes +* Returns the completion arguments as a tuple +* Propagates cancellation from the capy stop token + +=== Completion Signatures + +The result type depends on the Asio operation's completion signature: + +[cols="1,1"] +|=== +| Asio Signature | co_await Result + +| `void(error_code, std::size_t)` +| `std::tuple` + +| `void(error_code)` +| `std::tuple` + +| `void()` +| `std::tuple<>` +|=== + +Use structured bindings for clean syntax: + +[source,cpp] +---- +auto [ec, bytes] = co_await socket.async_read_some(buffer, capy::as_io_awaitable); +---- + +=== Setting as Default Token + +To avoid repeating `as_io_awaitable` on every call, rebind your I/O objects: + +[source,cpp] +---- +// Method 1: Type alias +using awaitable_socket = capy::as_io_awaitable_t::as_default_on_t< + asio::ip::tcp::socket>; + +awaitable_socket socket(io_context); +auto [ec, n] = co_await socket.async_read_some(buffer); // No token needed + +// Method 2: Runtime rebinding +auto socket = capy::as_io_awaitable_t::as_default_on( + asio::ip::tcp::socket(io_context)); +---- + +=== Cancellation + +When the capy coroutine receives a stop request (via its stop token), a terminal cancellation signal is sent to the Asio operation: + +[source,cpp] +---- +capy::io_task cancellable_read(asio::ip::tcp::socket& socket) +{ + std::array buffer; + + // If stop is requested, this operation will be cancelled + auto [ec, n] = co_await socket.async_read_some( + buffer, capy::as_io_awaitable); + + if (ec == asio::error::operation_aborted) + { + // Cancelled via stop token + co_return; + } + // ... +} +---- + +== Spawning Capy Coroutines from Asio + +The `asio_spawn` function allows you to run capy coroutines from Asio code, using any Asio completion token to handle the result. + +=== Basic Usage + +[source,cpp] +---- +#include +#include + +capy::io_task compute() +{ + // ... async work ... + co_return 42; +} + +int main() +{ + asio::io_context io; + + // Wrap the Asio executor for capy + auto exec = capy::wrap_asio_executor(io.get_executor()); + + // Spawn the coroutine, fire-and-forget + capy::asio_spawn(exec, compute())(asio::detached); + + io.run(); +} +---- + +=== Handling Results + +Use any Asio completion token to receive the result: + +[source,cpp] +---- +// Callback handler +capy::asio_spawn(exec, compute())( + [](std::exception_ptr ep, int result) { + if (ep) + std::rethrow_exception(ep); + std::cout << "Result: " << result << "\n"; + }); + +// With use_awaitable (in an Asio coroutine) +auto [ep, result] = co_await capy::asio_spawn(exec, compute())( + asio::use_awaitable); + +// With use_future +auto future = capy::asio_spawn(exec, compute())(asio::use_future); +auto [ep, result] = future.get(); +---- + +=== Completion Signature + +The completion signature depends on the coroutine's return type and exception specification: + +[cols="2,2"] +|=== +| Coroutine | Completion Signature + +| `io_task` (may throw) +| `void(std::exception_ptr, T)` + +| `io_task` (noexcept) +| `void(T)` + +| `io_task` (may throw) +| `void(std::exception_ptr)` + +| `io_task` (noexcept) +| `void()` +|=== + +=== Three-Argument Form + +A convenience overload accepts the token directly: + +[source,cpp] +---- +// Two-step form +capy::asio_spawn(exec, compute())(asio::detached); + +// Equivalent three-argument form +capy::asio_spawn(exec, compute(), asio::detached); +---- + +== Wrapping Executors + +=== Asio Executor to Capy + +Use `wrap_asio_executor` to adapt any Asio executor for capy: + +[source,cpp] +---- +asio::io_context io; +auto capy_exec = capy::wrap_asio_executor(io.get_executor()); + +// Now use with capy's run/run_async +capy::run_async(capy_exec)(my_task()); +---- + +The function automatically detects and handles: + +* Legacy Networking TS executors (with `on_work_started`/`on_work_finished`) +* Modern Boost.Asio standard executors +* Standalone Asio standard executors + +=== Capy Executor to Asio + +The `asio_executor_adapter` wraps a capy executor for use with Asio: + +[source,cpp] +---- +capy::thread_pool pool; +capy::asio_executor_adapter<> asio_exec(pool.get_executor()); + +// Use with Asio operations +asio::post(asio_exec, []{ std::cout << "Hello!\n"; }); +---- + +The adapter supports Asio's execution properties: + +* `blocking`: `possibly`, `never`, or `always` +* `outstanding_work`: `tracked` or `untracked` +* `allocator`: Custom allocator for handler allocation + +[source,cpp] +---- +// Require non-blocking execution +auto never_blocking = asio::require(asio_exec, + asio::execution::blocking.never); + +// Require work tracking +auto tracked = asio::require(asio_exec, + asio::execution::outstanding_work.tracked); +---- + +== Complete Example: Echo Server + +This example demonstrates a complete echo server using capy coroutines with Boost.Asio: + +[source,cpp] +---- +#include +#include +#include +#include + +namespace capy = boost::capy; +namespace asio = boost::asio; +using tcp = asio::ip::tcp; + +// Rebind socket to use as_io_awaitable by default +using socket_type = capy::as_io_awaitable_t::as_default_on_t; + +capy::io_task handle_client(socket_type socket) +{ + std::array buffer; + + for (;;) + { + // Read some data + auto [read_ec, bytes_read] = co_await socket.async_read_some( + asio::buffer(buffer)); + + if (read_ec) + { + if (read_ec != asio::error::eof) + std::cerr << "Read error: " << read_ec.message() << "\n"; + co_return; + } + + // Echo it back + auto [write_ec, bytes_written] = co_await asio::async_write( + socket, + asio::buffer(buffer.data(), bytes_read)); + + if (write_ec) + { + std::cerr << "Write error: " << write_ec.message() << "\n"; + co_return; + } + } +} + +capy::io_task accept_loop(tcp::acceptor& acceptor) +{ + auto exec = co_await capy::this_coro::executor; + + for (;;) + { + // Accept a connection + auto [ec, socket] = co_await acceptor.async_accept( + capy::as_io_awaitable); + + if (ec) + { + std::cerr << "Accept error: " << ec.message() << "\n"; + continue; + } + + // Spawn handler for this client (detached) + capy::asio_spawn(exec, handle_client( + socket_type(std::move(socket))))(asio::detached); + } +} + +int main() +{ + asio::io_context io; + + tcp::acceptor acceptor(io, {tcp::v4(), 8080}); + std::cout << "Listening on port 8080...\n"; + + auto exec = capy::wrap_asio_executor(io.get_executor()); + capy::asio_spawn(exec, accept_loop(acceptor))(asio::detached); + + io.run(); +} +---- + +== Reference + +[cols="1,2"] +|=== +| Header | Description + +| `` +| Complete Boost.Asio integration + +| `` +| Complete standalone Asio integration + +| `` +| The `as_io_awaitable` completion token (included by above) + +| `` +| The `asio_spawn` function (included by above) + +| `` +| The `asio_executor_adapter` class (included by above) + +| `` +| The `wrap_asio_executor` function (included by above) +|=== + +[cols="1,2"] +|=== +| Symbol | Description + +| `as_io_awaitable` +| Completion token for awaiting Asio operations + +| `as_io_awaitable_t::as_default_on` +| Rebind I/O objects to default to `as_io_awaitable` + +| `asio_spawn` +| Spawn capy coroutines with Asio completion tokens + +| `wrap_asio_executor` +| Adapt Asio executors for capy + +| `asio_executor_adapter` +| Adapt capy executors for Asio +|=== + diff --git a/include/boost/capy/asio/as_io_awaitable.hpp b/include/boost/capy/asio/as_io_awaitable.hpp index 99be89eb..9795bb0e 100644 --- a/include/boost/capy/asio/as_io_awaitable.hpp +++ b/include/boost/capy/asio/as_io_awaitable.hpp @@ -15,20 +15,74 @@ namespace boost::capy { +/** @defgroup asio Asio Integration + * @brief Components for integrating capy coroutines with Asio + * + * This module provides seamless integration between capy's coroutine + * framework and both Boost.Asio and standalone Asio. It enables: + * - Using capy coroutines as Asio completion tokens + * - Wrapping Asio executors for use with capy + * - Spawning capy coroutines from Asio contexts + * @{ + */ -struct as_io_awaitable_t +/** @brief Completion token for awaiting Asio async operations in capy coroutines. + * + * `as_io_awaitable_t` is a completion token type that allows Asio async + * operations to be `co_await`ed within capy's IO coroutines. When used as a + * completion token, the async operation returns an io-awaitable. + * + * @par Example + * @code + * capy::io_task read_some(asio::ip::tcp::socket& socket) + * { + * std::array buffer; + * auto [ec, n] = co_await socket.async_read_some( + * asio::buffer(buffer), + * capy::as_io_awaitable); + * if (ec) + * throw std::system_error(ec); + * co_return n; + * } + * @endcode + * + * @par Cancellation + * When the capy coroutine receives a stop request, a terminal cancellation + * signal is emitted to the underlying Asio operation. + * + * @see as_io_awaitable The global instance of this type + * @see executor_with_default For setting this as the default token + */ +struct as_io_awaitable_t { /// Default constructor. constexpr as_io_awaitable_t() { } - /// Adapts an executor to add the @c use_op_t completion token as the - /// default. + /** @brief Executor adapter that sets `as_io_awaitable_t` as the default token. + * + * This nested class template wraps an executor and specifies + * `as_io_awaitable_t` as the default completion token type. I/O objects + * using this executor will default to returning awaitables when no + * completion token is explicitly provided. + * + * @tparam InnerExecutor The underlying executor type to wrap + * + * @par Example + * @code + * using socket_type = asio::basic_stream_socket< + * asio::ip::tcp, + * as_io_awaitable_t::executor_with_default>; + * + * // Now async operations default to returning awaitables: + * auto bytes = co_await socket.async_read_some(buffer); + * @endcode + */ template struct executor_with_default : InnerExecutor { - /// Specify @c use_op_t as the default completion token type. + /// The default completion token type for I/O objects using this executor. typedef as_io_awaitable_t default_completion_token_type; executor_with_default(const InnerExecutor& ex) noexcept @@ -51,14 +105,43 @@ struct as_io_awaitable_t } }; - /// Type alias to adapt an I/O object to use @c use_op_t as its - /// default completion token type. + /** @brief Type alias to rebind an I/O object to use `as_io_awaitable_t` as default. + * + * Given an I/O object type `T` (e.g., `asio::ip::tcp::socket`), this alias + * produces a new type that uses `executor_with_default` and thus defaults + * to `as_io_awaitable_t` for all async operations. + * + * @tparam T The I/O object type to adapt (must support `rebind_executor`) + * + * @par Example + * @code + * using awaitable_socket = as_io_awaitable_t::as_default_on_t< + * asio::ip::tcp::socket>; + * @endcode + */ template using as_default_on_t = typename T::template rebind_executor< executor_with_default >::other; - /// Function helper to adapt an I/O object to use @c use_op_t as its - /// default completion token type. + /** @brief Adapts an I/O object instance to use `as_io_awaitable_t` as default. + * + * This function takes an existing I/O object and returns a new object of + * the same kind but rebound to use `executor_with_default`, making + * `as_io_awaitable_t` the default completion token. + * + * @tparam T The I/O object type (deduced) + * @param object The I/O object to adapt + * @return A new I/O object with `as_io_awaitable_t` as the default token + * + * @par Example + * @code + * asio::ip::tcp::socket raw_socket(io_context); + * auto socket = as_io_awaitable_t::as_default_on(std::move(raw_socket)); + * + * // Now you can omit the completion token: + * auto bytes = co_await socket.async_read_some(buffer); + * @endcode + */ template static typename std::decay_t::template rebind_executor< executor_with_default::executor_type> @@ -70,8 +153,19 @@ struct as_io_awaitable_t >::other(std::forward(object)); } }; + +/** @brief Global instance of `as_io_awaitable_t` for convenient use. + * + * Use this constant as a completion token to await Asio async operations. + * + * @par Example + * @code + * auto result = co_await socket.async_read_some(buffer, as_io_awaitable); + * @endcode + */ constexpr as_io_awaitable_t as_io_awaitable; +/** @} */ // end of asio group } diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 3136d3a1..4f106ea4 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -10,6 +10,41 @@ #ifndef BOOST_CAPY_ASIO_BOOST_HPP #define BOOST_CAPY_ASIO_BOOST_HPP +/** @file boost.hpp + * @brief Boost.Asio integration for capy coroutines. + * + * This header provides complete integration between capy's coroutine framework + * and Boost.Asio. Include this header when using capy with Boost.Asio. + * + * @par Features + * - Property query/require support for `asio_executor_adapter` + * - `asio_boost_standard_executor` wrapper for Boost.Asio executors + * - `async_result` specialization for `as_io_awaitable_t` + * - Three-argument `asio_spawn` overloads with completion tokens + * + * @par Example + * @code + * #include + * #include + * + * capy::io_task my_coro(boost::asio::ip::tcp::socket& sock) { + * char buf[1024]; + * auto [n] = co_await sock.async_read_some( + * boost::asio::buffer(buf), capy::as_io_awaitable); + * // ... + * } + * + * int main() { + * boost::asio::io_context io; + * auto exec = capy::wrap_asio_executor(io.get_executor()); + * capy::asio_spawn(exec, my_coro(socket))(boost::asio::detached); + * io.run(); + * } + * @endcode + * + * @see standalone.hpp For standalone Asio support + * @ingroup asio + */ #include #include @@ -36,10 +71,25 @@ namespace boost::capy { - +/** @addtogroup asio + * @{ + */ + +/// @name Execution Property Queries for asio_executor_adapter +/// @{ + +/** @brief Queries the execution context from an asio_executor_adapter. + * + * Returns the Boost.Asio execution context associated with the adapter. + * The context is provided via an adapter service that bridges capy's + * execution_context with Asio's. + * + * @param exec The executor adapter to query + * @return Reference to the associated `boost::asio::execution_context` + */ template -boost::asio::execution_context& - query(const asio_executor_adapter & exec, +boost::asio::execution_context& + query(const asio_executor_adapter & exec, boost::asio::execution::context_t) noexcept { using service = detail::asio_adapter_context_service< @@ -48,16 +98,20 @@ boost::asio::execution_context& template use_service(); } +/** @brief Queries the blocking property from an asio_executor_adapter. + * @param exec The executor adapter to query + * @return The current blocking property value + */ template -constexpr boost::asio::execution::blocking_t +constexpr boost::asio::execution::blocking_t query(const asio_executor_adapter & exec, boost::asio::execution::blocking_t) noexcept { switch (Bits & exec.blocking_mask) { - case exec.blocking_never: + case exec.blocking_never: return boost::asio::execution::blocking.never; - case exec.blocking_always: + case exec.blocking_always: return boost::asio::execution::blocking.always; case exec.blocking_possibly: return boost::asio::execution::blocking.possibly; @@ -65,94 +119,127 @@ constexpr boost::asio::execution::blocking_t } } +/// @} + +/// @name Execution Property Requirements for asio_executor_adapter +/// @{ + +/** @brief Requires blocking.possibly property. + * @return New adapter with blocking.possibly set + */ template constexpr auto - require(const asio_executor_adapter & exec, + require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::possibly_t) { constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; return asio_executor_adapter(exec); } +/** @brief Requires blocking.never property. + * @return New adapter that never blocks + */ template constexpr auto - require(const asio_executor_adapter & exec, - boost::asio::execution::blocking_t::never_t) + require(const asio_executor_adapter & exec, + boost::asio::execution::blocking_t::never_t) { constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_never; return asio_executor_adapter(exec); } +/** @brief Requires blocking.always property. + * @return New adapter that always blocks until execution completes + */ template constexpr auto - require(const asio_executor_adapter & exec, + require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::always_t) { constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_always; return asio_executor_adapter(exec); } +/** @brief Queries the outstanding_work property. + * @param exec The executor adapter to query + * @return The current work tracking setting + */ template static constexpr boost::asio::execution::outstanding_work_t query( - const asio_executor_adapter & exec, + const asio_executor_adapter & exec, boost::asio::execution::outstanding_work_t) noexcept { switch (Bits & exec.work_mask) { - case exec.work_tracked: + case exec.work_tracked: return boost::asio::execution::outstanding_work.tracked; - case exec.work_untracked: + case exec.work_untracked: return boost::asio::execution::outstanding_work.untracked; default: return {}; } } +/** @brief Requires outstanding_work.tracked property. + * @return New adapter that tracks outstanding work + */ template constexpr auto require(const asio_executor_adapter & exec, - boost::asio::execution::outstanding_work_t::tracked_t) + boost::asio::execution::outstanding_work_t::tracked_t) { constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; return asio_executor_adapter(exec); } +/** @brief Requires outstanding_work.untracked property. + * @return New adapter that does not track outstanding work + */ template constexpr auto - require(const asio_executor_adapter & exec, - boost::asio::execution::outstanding_work_t::untracked_t) + require(const asio_executor_adapter & exec, + boost::asio::execution::outstanding_work_t::untracked_t) { constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_untracked; return asio_executor_adapter(exec); } - +/** @brief Queries the allocator property. + * @return The adapter's current allocator + */ template constexpr Allocator query( - const asio_executor_adapter & exec, + const asio_executor_adapter & exec, boost::asio::execution::allocator_t) noexcept { return exec.get_allocator(); } +/** @brief Requires a specific allocator. + * @param a The allocator property containing the new allocator + * @return New adapter using the specified allocator + */ template constexpr auto require(const asio_executor_adapter & exec, - boost::asio::execution::allocator_t a) + boost::asio::execution::allocator_t a) { return asio_executor_adapter( exec, a.value() ); } +/** @brief Requires the default allocator (uses frame allocator). + * @return New adapter using the frame allocator from the context + */ template constexpr auto require(const asio_executor_adapter & exec, - boost::asio::execution::allocator_t a) + boost::asio::execution::allocator_t) noexcept(std::is_nothrow_move_constructible_v) { return asio_executor_adapter< - Executor, - std::pmr::polymorphic_allocator, + Executor, + std::pmr::polymorphic_allocator, Bits> ( exec, @@ -160,6 +247,8 @@ constexpr auto ); } +/// @} + namespace detail { @@ -167,8 +256,8 @@ template struct asio_work_tracker_service : boost::asio::execution_context::service { static boost::asio::execution_context::id id; - - asio_work_tracker_service(boost::asio::execution_context & ctx) + + asio_work_tracker_service(boost::asio::execution_context & ctx) : boost::asio::execution_context::service(ctx) {} using tracked_executor = @@ -176,9 +265,9 @@ struct asio_work_tracker_service : boost::asio::execution_context::service Executor, boost::asio::execution::outstanding_work_t::tracked_t >::type; - + alignas(tracked_executor) char buffer[sizeof(tracked_executor) ]; - + std::atomic_size_t work = 0u; void shutdown() @@ -192,11 +281,11 @@ struct asio_work_tracker_service : boost::asio::execution_context::service { if (work.fetch_add(1u) == 0u) new (buffer) tracked_executor( - boost::asio::prefer(exec, + boost::asio::prefer(exec, boost::asio::execution::outstanding_work.tracked)); } - void work_finished() + void work_finished() { if (work.fetch_sub(1u) == 1u) reinterpret_cast(buffer)->~tracked_executor(); @@ -210,34 +299,69 @@ boost::asio::execution_context::id asio_work_tracker_service::id; } +/** @brief Wraps a Boost.Asio standard executor for use with capy. + * + * This class adapts Boost.Asio executors that follow the P0443/P2300 + * standard executor model to be usable as capy executors. It provides + * work tracking through an `asio_work_tracker_service` and integrates + * with capy's execution context system. + * + * @tparam Executor A Boost.Asio executor satisfying `AsioBoostStandardExecutor` + * + * @par Example + * @code + * boost::asio::io_context io; + * auto wrapped = asio_boost_standard_executor(io.get_executor()); + * + * // Use with capy coroutines + * capy::run(wrapped, my_io_task()); + * @endcode + * + * @see wrap_asio_executor For automatic executor type detection + * @see asio_net_ts_executor For legacy Networking TS executors + */ template struct asio_boost_standard_executor { - - asio_boost_standard_executor(Executor executor) - noexcept(std::is_nothrow_move_constructible_v) - : executor_(std::move(executor)) + /** @brief Constructs from a Boost.Asio executor. + * @param executor The Boost.Asio executor to wrap + */ + asio_boost_standard_executor(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) { } - asio_boost_standard_executor(asio_boost_standard_executor && rhs) - noexcept(std::is_nothrow_move_constructible_v) - : executor_(std::move(rhs.executor_)) + + /** @brief Move constructor. */ + asio_boost_standard_executor(asio_boost_standard_executor && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) { } - asio_boost_standard_executor(const asio_boost_standard_executor & rhs) - noexcept(std::is_nothrow_copy_constructible_v) + + /** @brief Copy constructor. */ + asio_boost_standard_executor(const asio_boost_standard_executor & rhs) + noexcept(std::is_nothrow_copy_constructible_v) : executor_(rhs.executor_) { } + /** @brief Returns the associated capy execution context. + * @return Reference to the capy execution_context + */ execution_context& context() const noexcept { auto & ec = boost::asio::query(executor_, boost::asio::execution::context); return boost::asio::use_service< detail::asio_context_service - >(ec); + >(ec); } + /** @brief Notifies that work has started. + * + * Delegates to the work tracker service to maintain a tracked executor + * while work is outstanding. + */ void on_work_started() const noexcept { auto & ec = boost::asio::query(executor_, boost::asio::execution::context); @@ -246,6 +370,10 @@ struct asio_boost_standard_executor >(ec).work_started(executor_); } + /** @brief Notifies that work has finished. + * + * Decrements the work counter in the tracker service. + */ void on_work_finished() const noexcept { auto & ec = boost::asio::query(executor_, boost::asio::execution::context); @@ -254,7 +382,14 @@ struct asio_boost_standard_executor >(ec).work_finished(); } - + /** @brief Dispatches a continuation for execution. + * + * May execute inline if the executor allows, otherwise posts. + * Uses the context's frame allocator for handler allocation. + * + * @param c The continuation to dispatch + * @return A noop coroutine handle + */ std::coroutine_handle<> dispatch(continuation & c) const { boost::asio::prefer( @@ -268,6 +403,13 @@ struct asio_boost_standard_executor return std::noop_coroutine(); } + /** @brief Posts a continuation for deferred execution. + * + * The continuation will never be executed inline. Uses blocking.never + * and relationship.fork properties for proper async behavior. + * + * @param c The continuation to post + */ void post(continuation & c) const { boost::asio::prefer( @@ -280,11 +422,15 @@ struct asio_boost_standard_executor ) ).execute(detail::asio_coroutine_unique_handle(c.h)); } - bool operator==(const asio_boost_standard_executor & rhs) const noexcept - { + + /** @brief Equality comparison. */ + bool operator==(const asio_boost_standard_executor & rhs) const noexcept + { return executor_ == rhs.executor_; } - bool operator!=(const asio_boost_standard_executor & rhs) const noexcept + + /** @brief Inequality comparison. */ + bool operator!=(const asio_boost_standard_executor & rhs) const noexcept { return executor_ != rhs.executor_; } @@ -294,7 +440,20 @@ struct asio_boost_standard_executor }; -template @@ -304,7 +463,19 @@ auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) return asio_spawn(exec, std::forward(runnable))(std::move(token)); } -template @@ -314,13 +485,15 @@ auto asio_spawn(Context & ctx, Runnable && runnable, Token token) return asio_spawn(ctx.get_executor(), std::forward(runnable))(std::move(token)); } +/** @} */ // end of asio group + } template struct boost::asio::async_result : boost::capy::detail::async_result_impl< - boost::asio::cancellation_signal, - boost::asio::cancellation_type, + boost::asio::cancellation_signal, + boost::asio::cancellation_type, Ts...> { }; @@ -330,15 +503,15 @@ namespace boost::capy::detail { - struct boost_asio_init; + template struct boost_asio_promise_type_allocator_base { template - void * operator new (std::size_t n, boost_asio_init &, - Handler & handler, + void * operator new (std::size_t n, boost_asio_init &, + Handler & handler, Ex &, Runnable &) { using allocator_type = std::allocator_traits @@ -375,7 +548,7 @@ struct boost_asio_promise_type_allocator_base template -struct boost_asio_init_promise_type +struct boost_asio_init_promise_type : boost_asio_promise_type_allocator_base< boost::asio::associated_allocator_t> { @@ -528,12 +701,11 @@ struct boost_asio_init_promise_type }; - struct boost_asio_init { - template + template void operator()( - Handler , + Handler , Ex, Runnable runnable) { @@ -542,25 +714,25 @@ struct boost_asio_init } }; -template - requires +template + requires boost::asio::completion_token_for< - Token, + Token, completion_signature_for_io_runnable > struct initialize_asio_spawn_helper { template - static auto init(Executor ex, Runnable r, Token && tk) + static auto init(Executor ex, Runnable r, Token && tk) -> decltype( boost::asio::async_initiate< - Token, + Token, completion_signature_for_io_runnable>( boost_asio_init{}, tk, std::move(ex), std::move(r) )) { return boost::asio::async_initiate< - Token, + Token, completion_signature_for_io_runnable>( boost_asio_init{}, tk, std::move(ex), std::move(r) @@ -568,20 +740,21 @@ struct initialize_asio_spawn_helper } }; + } template -struct std::coroutine_traits { - using promise_type + using promise_type = boost::capy::detail::boost_asio_init_promise_type< - Handler, - Executor, + Handler, + Executor, Runnable>; }; diff --git a/include/boost/capy/asio/executor_adapter.hpp b/include/boost/capy/asio/executor_adapter.hpp index f8efc096..6b34978c 100644 --- a/include/boost/capy/asio/executor_adapter.hpp +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -18,12 +18,26 @@ namespace boost { namespace capy { + +/** @addtogroup asio + * @{ + */ + namespace detail { +/** @brief Service that bridges capy's execution_context with Asio's. + * @internal + * + * This service inherits from both capy's service and the target Asio + * execution context, allowing capy executors wrapped in `asio_executor_adapter` + * to be queried for their Asio execution context. + * + * @tparam ExecutionContext The Asio execution_context type (boost::asio or standalone) + */ template -struct asio_adapter_context_service - : execution_context::service, +struct asio_adapter_context_service + : execution_context::service, // shutdown is protected ExecutionContext { @@ -34,33 +48,96 @@ struct asio_adapter_context_service } +/** @brief Adapts a capy executor to be usable with Asio. + * + * `asio_executor_adapter` wraps a capy executor and exposes it as an + * Asio-compatible executor. This allows capy coroutines and executors to + * interoperate seamlessly with Asio's async operations. + * + * The adapter tracks execution properties (blocking behavior and work tracking) + * as compile-time template parameters for zero-overhead property queries. + * + * @tparam Executor The underlying capy executor type (default: `capy::any_executor`) + * @tparam Allocator The allocator type for handler allocation + * (default: `std::pmr::polymorphic_allocator`) + * @tparam Bits Compile-time bitfield encoding blocking and work-tracking properties + * + * @par Execution Properties + * The adapter supports the standard Asio execution properties: + * - `blocking`: `possibly` (default), `never`, or `always` + * - `outstanding_work`: `untracked` (default) or `tracked` + * - `allocator`: Custom allocator for handler allocation + * - `context`: Returns the associated capy execution_context + * + * @par Example + * @code + * // Wrap a capy executor for use with Asio + * capy::any_executor capy_exec = ...; + * asio_executor_adapter<> asio_exec(capy_exec); + * + * // Use with Asio operations + * asio::post(asio_exec, []{ std::cout << "Hello from capy!\n"; }); + * + * // Require non-blocking execution + * auto never_blocking = asio::require(asio_exec, + * asio::execution::blocking.never); + * @endcode + * + * @see wrap_asio_executor For the reverse direction (Asio to capy) + */ template, + typename Allocator = std::pmr::polymorphic_allocator, int Bits = 0> struct asio_executor_adapter { - constexpr static int blocking_possibly = 0b000; - constexpr static int blocking_never = 0b001; - constexpr static int blocking_always = 0b010; - constexpr static int blocking_mask = 0b011; - constexpr static int work_untracked = 0b000; - constexpr static int work_tracked = 0b100; - constexpr static int work_mask = 0b100; + /// @name Blocking Property Constants + /// @{ + constexpr static int blocking_possibly = 0b000; ///< May block the caller + constexpr static int blocking_never = 0b001; ///< Never blocks the caller + constexpr static int blocking_always = 0b010; ///< Always blocks until complete + constexpr static int blocking_mask = 0b011; ///< Mask for blocking bits + /// @} + + /// @name Work Tracking Property Constants + /// @{ + constexpr static int work_untracked = 0b000; ///< Work is not tracked + constexpr static int work_tracked = 0b100; ///< Outstanding work is tracked + constexpr static int work_mask = 0b100; ///< Mask for work tracking bit + /// @} + /// @name Constructors + /// @{ + + /** @brief Copy constructor from adapter with different property bits. + * + * Creates a copy with potentially different execution properties. + * If this adapter tracks work, `on_work_started()` is called. + * + * @tparam Bits_ The source adapter's property bits + * @param rhs The source adapter to copy from + */ template asio_executor_adapter( const asio_executor_adapter & rhs) noexcept(std::is_nothrow_copy_constructible_v) : executor_(rhs.executor_), allocator_(rhs.allocator_) - { + { if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_started(); } - + + /** @brief Move constructor from adapter with different property bits. + * + * Moves from another adapter with potentially different properties. + * If this adapter tracks work, `on_work_started()` is called. + * + * @tparam Bits_ The source adapter's property bits + * @param rhs The source adapter to move from + */ template asio_executor_adapter( - asio_executor_adapter && rhs) + asio_executor_adapter && rhs) noexcept(std::is_nothrow_move_constructible_v) : executor_(std::move(rhs.executor_)) , allocator_(std::move(rhs.allocator_)) @@ -68,9 +145,14 @@ struct asio_executor_adapter if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_started(); } - - asio_executor_adapter(Executor executor, const Allocator & alloc) - noexcept(std::is_nothrow_move_constructible_v + + /** @brief Constructs from executor and allocator. + * + * @param executor The capy executor to wrap + * @param alloc The allocator for handler allocation + */ + asio_executor_adapter(Executor executor, const Allocator & alloc) + noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_copy_constructible_v) : executor_(std::move(executor)), allocator_(alloc) { @@ -78,20 +160,32 @@ struct asio_executor_adapter executor_.on_work_started(); } + /** @brief Constructs from adapter with different allocator. + * + * @tparam OtherAllocator The source adapter's allocator type + * @param executor The source adapter + * @param alloc The new allocator to use + */ template explicit asio_executor_adapter( - asio_executor_adapter executor, - const Allocator & alloc) - noexcept(std::is_nothrow_move_constructible_v && + asio_executor_adapter executor, + const Allocator & alloc) + noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_copy_constructible_v) : executor_(std::move(executor.executor_)), allocator_(alloc) { if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_started(); } - - - asio_executor_adapter(Executor executor) + + + /** @brief Constructs from a capy executor. + * + * The allocator is obtained from the executor's context frame allocator. + * + * @param executor The capy executor to wrap + */ + asio_executor_adapter(Executor executor) noexcept(std::is_nothrow_move_constructible_v) : executor_(std::move(executor)) , allocator_(executor_.context().get_frame_allocator()) @@ -100,18 +194,35 @@ struct asio_executor_adapter executor_.on_work_started(); } + /** @brief Destructor. + * + * If work tracking is enabled, calls `on_work_finished()`. + */ ~asio_executor_adapter() { if constexpr((Bits & work_mask) == work_tracked) executor_.on_work_finished(); } + /// @} + + /// @name Assignment + /// @{ + + /** @brief Copy assignment from adapter with different property bits. + * + * Properly handles work tracking when changing executors. + * + * @tparam Bits_ The source adapter's property bits + * @param rhs The source adapter + * @return Reference to this adapter + */ template asio_executor_adapter & operator=( - const asio_executor_adapter & rhs) + const asio_executor_adapter & rhs) { - - if constexpr((Bits & work_mask) == work_tracked) + + if constexpr((Bits & work_mask) == work_tracked) if (rhs.executor_ != executor_) { rhs.executor_.on_work_started(); @@ -122,17 +233,46 @@ struct asio_executor_adapter allocator_ = rhs.allocator_; } - bool operator==(const asio_executor_adapter & rhs) const noexcept - { + /// @} + + /// @name Comparison + /// @{ + + /** @brief Equality comparison. + * @param rhs The adapter to compare with + * @return `true` if both executor and allocator are equal + */ + 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 + + /** @brief Inequality comparison. + * @param rhs The adapter to compare with + * @return `true` if executor or allocator differs + */ + bool operator!=(const asio_executor_adapter & rhs) const noexcept { return executor_ != rhs.executor_ && allocator_ != rhs.allocator_; - } + } + + /// @} + /// @name Execution + /// @{ + + /** @brief Executes a function according to the blocking property. + * + * The execution behavior depends on the `Bits` template parameter: + * - `blocking_never`: Posts the function for deferred execution + * - `blocking_possibly`: Dispatches (may run inline or post) + * - `blocking_always`: Executes the function inline immediately + * + * @tparam Function The callable type + * @param f The function to execute + */ template void execute(Function&& f) const { @@ -144,22 +284,41 @@ struct asio_executor_adapter detail::make_continuation(std::forward(f), allocator_) ).resume(); else if constexpr((Bits & blocking_mask) == blocking_always) - std::forward(f)(); + std::forward(f)(); } + /// @} + + /// @name Accessors + /// @{ + + /** @brief Returns the associated execution context. + * @return Reference to the capy execution_context + */ execution_context & context() const {return executor_.context(); } + + /** @brief Returns the associated allocator. + * @return Copy of the allocator + */ Allocator get_allocator() const noexcept {return allocator_;} - const Executor get_capy_executor() const {return executor_;} + /** @brief Returns the underlying capy executor. + * @return Copy of the wrapped executor + */ + const Executor get_capy_executor() const {return executor_;} + + /// @} private: template friend struct asio_executor_adapter; Executor executor_; [[no_unique_address]] Allocator allocator_; - + }; +/** @} */ // end of asio group + } } diff --git a/include/boost/capy/asio/executor_from_asio.hpp b/include/boost/capy/asio/executor_from_asio.hpp index 56dddafb..948e0584 100644 --- a/include/boost/capy/asio/executor_from_asio.hpp +++ b/include/boost/capy/asio/executor_from_asio.hpp @@ -21,11 +21,25 @@ namespace boost { namespace capy { + +/** @addtogroup asio + * @{ + */ + namespace detail { +/** @brief Concept for legacy Networking TS style Asio executors. + * @internal + * + * Matches executors that provide the original Networking TS executor interface + * with explicit work counting (`on_work_started`/`on_work_finished`) and + * `dispatch`/`post` methods that take a handler and allocator. + * + * @tparam Executor The type to check + */ template -concept AsioNetTsExecutor = requires (Executor exec, +concept AsioNetTsExecutor = requires (Executor exec, std::coroutine_handle<> h, std::pmr::polymorphic_allocator a) { @@ -36,71 +50,143 @@ concept AsioNetTsExecutor = requires (Executor exec, exec.context(); } ; +/** @brief Concept for Boost.Asio standard executors. + * @internal + * + * Matches executors compatible with the P0443/P2300 executor model as + * implemented in Boost.Asio. These executors use the property query/require + * mechanism and return `boost::asio::execution_context&` from context queries. + * + * @tparam Executor The type to check + */ template concept AsioBoostStandardExecutor = std::same_as< typename boost::asio::query_result< - Executor, - boost::asio::execution::detail::context_t<0>>::type, + Executor, + boost::asio::execution::detail::context_t<0>>::type, boost::asio::execution_context&>; +/** @brief Concept for standalone Asio standard executors. + * @internal + * + * Matches executors compatible with the P0443/P2300 executor model as + * implemented in standalone Asio. These executors return + * `::asio::execution_context&` from context queries. + * + * @tparam Executor The type to check + */ template concept AsioStandaloneStandardExecutor = std::same_as< typename ::asio::query_result< - Executor, - ::asio::execution::detail::context_t<0>>::type, + Executor, + ::asio::execution::detail::context_t<0>>::type, ::asio::execution_context&>; } +/** @brief Wraps a legacy Networking TS executor for use with capy. + * + * This class adapts Asio executors that follow the original Networking TS + * executor model (with `on_work_started`/`on_work_finished` and + * `dispatch`/`post` methods) to be usable as capy executors. + * + * @tparam Executor An executor type satisfying `AsioNetTsExecutor` + * + * @par Example + * @code + * boost::asio::io_context io; + * auto wrapped = asio_net_ts_executor(io.get_executor()); + * + * // Use with capy coroutines + * capy::run(wrapped, my_coroutine()); + * @endcode + * + * @see wrap_asio_executor For automatic executor type detection + */ template struct asio_net_ts_executor { - asio_net_ts_executor(Executor executor) - noexcept(std::is_nothrow_move_constructible_v) - : executor_(std::move(executor)) + /** @brief Constructs from an Asio Net.TS executor. + * @param executor The Asio executor to wrap + */ + asio_net_ts_executor(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) { } - asio_net_ts_executor(asio_net_ts_executor && rhs) - noexcept(std::is_nothrow_move_constructible_v) - : executor_(std::move(rhs.executor_)) + + /** @brief Move constructor. */ + asio_net_ts_executor(asio_net_ts_executor && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) { } - asio_net_ts_executor(const asio_net_ts_executor & rhs) - noexcept(std::is_nothrow_copy_constructible_v) + + /** @brief Copy constructor. */ + asio_net_ts_executor(const asio_net_ts_executor & rhs) + noexcept(std::is_nothrow_copy_constructible_v) : executor_(rhs.executor_) { } + /** @brief Returns the associated capy execution context. + * + * The context is obtained via an `asio_context_service` registered + * with the underlying Asio execution context. + * + * @return Reference to the capy execution_context + */ execution_context& context() const noexcept { using ex_t = std::remove_reference_t; return use_service> ( executor_.context() - ); + ); } + /** @brief Notifies that work has started. + * + * Forwards to the underlying executor's `on_work_started()`. + */ void on_work_started() const noexcept { executor_.on_work_started(); } + /** @brief Notifies that work has finished. + * + * Forwards to the underlying executor's `on_work_finished()`. + */ void on_work_finished() const noexcept { executor_.on_work_finished(); } + /** @brief Dispatches a continuation for execution. + * + * May execute inline if allowed by the executor, otherwise posts. + * + * @param c The continuation to dispatch + * @return A noop coroutine handle (execution is delegated to Asio) + */ std::coroutine_handle<> dispatch(continuation & c) const { executor_.dispatch( - detail::asio_coroutine_unique_handle(c.h), + detail::asio_coroutine_unique_handle(c.h), std::pmr::polymorphic_allocator( boost::capy::get_current_frame_allocator())); - + return std::noop_coroutine(); } + /** @brief Posts a continuation for deferred execution. + * + * The continuation will never be executed inline. + * + * @param c The continuation to post + */ void post(continuation & c) const { executor_.post( @@ -109,11 +195,14 @@ struct asio_net_ts_executor boost::capy::get_current_frame_allocator())); } - bool operator==(const asio_net_ts_executor & rhs) const noexcept - { + /** @brief Equality comparison. */ + bool operator==(const asio_net_ts_executor & rhs) const noexcept + { return executor_ == rhs.executor_; } - bool operator!=(const asio_net_ts_executor & rhs) const noexcept + + /** @brief Inequality comparison. */ + bool operator!=(const asio_net_ts_executor & rhs) const noexcept { return executor_ != rhs.executor_; } @@ -123,13 +212,50 @@ struct asio_net_ts_executor }; +/** @brief Wraps a Boost.Asio standard executor for use with capy. + * + * Forward declaration; defined in `boost.hpp`. + * + * @tparam Executor An executor type satisfying `AsioBoostStandardExecutor` + * @see asio_net_ts_executor For legacy executor wrapping + */ template struct asio_boost_standard_executor; +/** @brief Wraps a standalone Asio standard executor for use with capy. + * + * Forward declaration; defined in `standalone.hpp`. + * + * @tparam Executor An executor type satisfying `AsioStandaloneStandardExecutor` + * @see asio_net_ts_executor For legacy executor wrapping + */ template struct asio_standalone_standard_executor; +/** @brief Automatically wraps any Asio executor for use with capy. + * + * This function detects the type of Asio executor and returns the appropriate + * capy-compatible wrapper: + * - Legacy Net.TS executors -> `asio_net_ts_executor` + * - Boost.Asio standard executors -> `asio_boost_standard_executor` + * - Standalone Asio standard executors -> `asio_standalone_standard_executor` + * + * @tparam Executor The Asio executor type (deduced) + * @param exec The Asio executor to wrap + * @return A capy-compatible executor wrapping the input + * + * @par Example + * @code + * boost::asio::io_context io; + * auto capy_exec = wrap_asio_executor(io.get_executor()); + * + * // Now use with capy + * capy::run(capy_exec, my_io_task()); + * @endcode + * + * @note Fails to compile with a static_assert if the executor type is not recognized. + */ template auto wrap_asio_executor(Executor && exec) { @@ -151,10 +277,19 @@ auto wrap_asio_executor(Executor && exec) }; +/** @brief Type alias for the result of `wrap_asio_executor`. + * + * Given an Asio executor type, this alias yields the corresponding + * capy wrapper type. + * + * @tparam Executor The Asio executor type + */ template -using wrap_asio_executor_t +using wrap_asio_executor_t = decltype(wrap_asio_executor(std::declval())); +/** @} */ // end of asio group + } diff --git a/include/boost/capy/asio/spawn.hpp b/include/boost/capy/asio/spawn.hpp index 89651110..cadb66cf 100644 --- a/include/boost/capy/asio/spawn.hpp +++ b/include/boost/capy/asio/spawn.hpp @@ -22,26 +22,51 @@ namespace boost::capy { +/** @addtogroup asio + * @{ + */ + namespace detail { -template +/** @brief Helper for initializing spawned coroutines with Boost.Asio tokens. + * @internal + * + * Specialized in `boost.hpp` for actual Boost.Asio completion tokens. + */ +template struct initialize_asio_spawn_helper; - +/** @brief Concept for valid Boost.Asio spawn completion tokens. + * @internal + * + * A token satisfies this concept if `initialize_asio_spawn_helper` can + * initialize the spawn operation with it. + */ template -concept asio_spawn_token = +concept asio_spawn_token = requires (Token && tk, Executor ex, Runnable rn) { initialize_asio_spawn_helper:: init(std::move(ex), std::move(rn), std::forward(tk)); }; -template +/** @brief Helper for initializing spawned coroutines with standalone Asio tokens. + * @internal + * + * Specialized in `standalone.hpp` for actual standalone Asio completion tokens. + */ +template struct initialize_asio_standalone_spawn_helper; +/** @brief Concept for valid standalone Asio spawn completion tokens. + * @internal + * + * A token satisfies this concept if `initialize_asio_standalone_spawn_helper` + * can initialize the spawn operation with it. + */ template -concept asio_standalone_spawn_token = +concept asio_standalone_spawn_token = requires (Token && tk, Executor ex, Runnable rn) { initialize_asio_standalone_spawn_helper:: @@ -51,52 +76,142 @@ concept asio_standalone_spawn_token = } +/** @brief Deferred spawn operation that can be initiated with a completion token. + * + * This class represents a spawn operation that has captured an executor and + * runnable but hasn't been initiated yet. Call `operator()` with a completion + * token to start the operation. + * + * @tparam Executor The executor type for running the coroutine + * @tparam Runnable The coroutine type (must satisfy `IoRunnable`) + * + * @par Example + * @code + * auto op = asio_spawn(executor, my_coroutine()); + * + * // Initiate with different tokens: + * op(asio::detached); // Fire and forget + * op([](std::exception_ptr) { ... }); // Callback on completion + * co_await op(asio::use_awaitable); // Await in another coroutine + * @endcode + * + * @see asio_spawn Factory function to create spawn operations + */ template struct asio_spawn_op { - asio_spawn_op(Executor executor, Runnable runnable) - : executor_(std::move(executor)), runnable_(std::move(runnable)) + /** @brief Constructs the spawn operation. + * @param executor The executor to run on + * @param runnable The coroutine to spawn + */ + asio_spawn_op(Executor executor, Runnable runnable) + : executor_(std::move(executor)), runnable_(std::move(runnable)) {} + /** @brief Initiates the spawn with a Boost.Asio completion token. + * + * @tparam Token A valid Boost.Asio completion token + * @param token The completion token determining how to handle completion + * @return Depends on the token (e.g., void for callbacks, awaitable for use_awaitable) + */ template Token> auto operator()(Token && token) { return detail::initialize_asio_spawn_helper::init( - std::move(executor_), - std::move(runnable_), + std::move(executor_), + std::move(runnable_), std::forward(token) ); } - + + /** @brief Initiates the spawn with a standalone Asio completion token. + * + * @tparam Token A valid standalone Asio completion token + * @param token The completion token determining how to handle completion + * @return Depends on the token + */ template Token> auto operator()(Token && token) { return detail::initialize_asio_standalone_spawn_helper::init ( - std::move(executor_), - std::move(runnable_), + std::move(executor_), + std::move(runnable_), std::forward(token) ); } - + private: Executor executor_; - Runnable runnable_; + Runnable runnable_; }; +/** @brief Spawns a capy coroutine for execution with an Asio completion token. + * + * Creates a deferred spawn operation that can be initiated with any Asio + * completion token. The coroutine will run on the specified executor. + * + * @tparam ExecutorType The executor type (must satisfy `Executor`) + * @tparam Runnable The coroutine type (must satisfy `IoRunnable`) + * @param exec The executor to run the coroutine on + * @param runnable The coroutine to spawn + * @return An `asio_spawn_op` that can be called with a completion token + * + * @par Completion Signature + * The completion signature depends on the coroutine's return type: + * - `void` return, noexcept: `void()` + * - `void` return, may throw: `void(std::exception_ptr)` + * - `T` return, noexcept: `void(T)` + * - `T` return, may throw: `void(std::exception_ptr, T)` + * + * @par Example + * @code + * capy::io_task compute() { co_return 42; } + * + * // Using with Boost.Asio + * asio_spawn(executor, compute())( + * [](std::exception_ptr ep, int result) { + * if (!ep) std::cout << "Result: " << result << "\n"; + * }); + * + * // Using with asio::use_awaitable + * auto [ep, result] = co_await asio_spawn(executor, compute())(asio::use_awaitable); + * @endcode + * + * @see asio_spawn_op The returned operation type + */ template auto asio_spawn(ExecutorType exec, Runnable && runnable) { return asio_spawn_op(std::move(exec), std::forward(runnable)); } +/** @brief Spawns a capy coroutine using a context's executor. + * + * Convenience overload that extracts the executor from an execution context. + * + * @tparam Context The execution context type (must satisfy `ExecutionContext`) + * @tparam Runnable The coroutine type (must satisfy `IoRunnable`) + * @param ctx The execution context providing the executor + * @param runnable The coroutine to spawn + * @return An `asio_spawn_op` that can be called with a completion token + * + * @par Example + * @code + * boost::asio::io_context io; + * asio_spawn(io, my_coroutine())(asio::detached); + * io.run(); + * @endcode + */ template auto asio_spawn(Context & ctx, Runnable && runnable) { return asio_spawn_op(ctx.get_executor(), std::forward(runnable)); } +/** @} */ // end of asio group + } #endif diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 50e8cb81..ff68797c 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -10,6 +10,42 @@ #ifndef BOOST_CAPY_ASIO_STANDALONE_HPP #define BOOST_CAPY_ASIO_STANDALONE_HPP +/** @file standalone.hpp + * @brief Standalone Asio integration for capy coroutines. + * + * This header provides complete integration between capy's coroutine framework + * and standalone Asio (without Boost). Include this header when using capy + * with standalone Asio. + * + * @par Features + * - Property query/require support for `asio_executor_adapter` + * - `asio_standalone_standard_executor` wrapper for standalone Asio executors + * - `async_result` specialization for `as_io_awaitable_t` + * - Three-argument `asio_spawn` overloads with completion tokens + * + * @par Example + * @code + * #include + * #include + * + * capy::io_task my_coro(asio::ip::tcp::socket& sock) { + * char buf[1024]; + * auto [n] = co_await sock.async_read_some( + * asio::buffer(buf), capy::as_io_awaitable); + * // ... + * } + * + * int main() { + * asio::io_context io; + * auto exec = capy::wrap_asio_executor(io.get_executor()); + * capy::asio_spawn(exec, my_coro(socket))(asio::detached); + * io.run(); + * } + * @endcode + * + * @see boost.hpp For Boost.Asio support + * @ingroup asio + */ #include #include @@ -36,10 +72,20 @@ namespace boost::capy { +/** @addtogroup asio + * @{ + */ +/// @name Execution Property Queries for asio_executor_adapter (Standalone Asio) +/// @{ + +/** @brief Queries the execution context from an asio_executor_adapter. + * @param exec The executor adapter to query + * @return Reference to the associated `::asio::execution_context` + */ template -::asio::execution_context& - query(const asio_executor_adapter & exec, +::asio::execution_context& + query(const asio_executor_adapter & exec, ::asio::execution::context_t) noexcept { using service = detail::asio_adapter_context_service< @@ -48,8 +94,12 @@ ::asio::execution_context& template use_service(); } +/** @brief Queries the blocking property. + * @param exec The executor adapter to query + * @return The current blocking property value + */ template -constexpr ::asio::execution::blocking_t +constexpr ::asio::execution::blocking_t query(const asio_executor_adapter & exec, ::asio::execution::blocking_t) noexcept { @@ -62,94 +112,127 @@ constexpr ::asio::execution::blocking_t } } +/// @} + +/// @name Execution Property Requirements for asio_executor_adapter (Standalone Asio) +/// @{ + +/** @brief Requires blocking.possibly property. + * @return New adapter with blocking.possibly set + */ template constexpr auto - require(const asio_executor_adapter & exec, + require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::possibly_t) { constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; return asio_executor_adapter(exec); } +/** @brief Requires blocking.never property. + * @return New adapter that never blocks + */ template constexpr auto require(const asio_executor_adapter & exec, - ::asio::execution::blocking_t::never_t) + ::asio::execution::blocking_t::never_t) { constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_never; return asio_executor_adapter(exec); } +/** @brief Requires blocking.always property. + * @return New adapter that always blocks until execution completes + */ template constexpr auto - require(const asio_executor_adapter & exec, + require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::always_t) { constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_always; return asio_executor_adapter(exec); } +/** @brief Queries the outstanding_work property. + * @param exec The executor adapter to query + * @return The current work tracking setting + */ template static constexpr ::asio::execution::outstanding_work_t query( - const asio_executor_adapter & exec, + const asio_executor_adapter & exec, ::asio::execution::outstanding_work_t) noexcept { switch (Bits & exec.work_mask) { - case exec.work_tracked: + case exec.work_tracked: return ::asio::execution::outstanding_work.tracked; - case exec.work_untracked: + case exec.work_untracked: return ::asio::execution::outstanding_work.untracked; default: return {}; } } +/** @brief Requires outstanding_work.tracked property. + * @return New adapter that tracks outstanding work + */ template constexpr auto - require(const asio_executor_adapter & exec, - ::asio::execution::outstanding_work_t::tracked_t) + require(const asio_executor_adapter & exec, + ::asio::execution::outstanding_work_t::tracked_t) { constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; return asio_executor_adapter(exec); } +/** @brief Requires outstanding_work.untracked property. + * @return New adapter that does not track outstanding work + */ template constexpr auto - require(const asio_executor_adapter & exec, - ::asio::execution::outstanding_work_t::untracked_t) + require(const asio_executor_adapter & exec, + ::asio::execution::outstanding_work_t::untracked_t) { constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_untracked; return asio_executor_adapter(exec); } - +/** @brief Queries the allocator property. + * @return The adapter's current allocator + */ template constexpr Allocator query( - const asio_executor_adapter & exec, + const asio_executor_adapter & exec, ::asio::execution::allocator_t) noexcept { return exec.get_allocator(); } +/** @brief Requires a specific allocator. + * @param a The allocator property containing the new allocator + * @return New adapter using the specified allocator + */ template constexpr auto require(const asio_executor_adapter & exec, - ::asio::execution::allocator_t a) + ::asio::execution::allocator_t a) { return asio_executor_adapter( exec, a.value() ); } +/** @brief Requires the default allocator (uses frame allocator). + * @return New adapter using the frame allocator from the context + */ template constexpr auto require(const asio_executor_adapter & exec, - ::asio::execution::allocator_t a) + ::asio::execution::allocator_t a) noexcept(std::is_nothrow_move_constructible_v) { return asio_executor_adapter< - Executor, - std::pmr::polymorphic_allocator, + Executor, + std::pmr::polymorphic_allocator, Bits> ( exec, @@ -157,16 +240,18 @@ constexpr auto ); } +/// @} + namespace detail { template -struct asio_standalone_work_tracker_service : +struct asio_standalone_work_tracker_service : ::asio::execution_context::service { static ::asio::execution_context::id id; - - asio_standalone_work_tracker_service(::asio::execution_context & ctx) + + asio_standalone_work_tracker_service(::asio::execution_context & ctx) : ::asio::execution_context::service(ctx) {} using tracked_executor = @@ -213,19 +298,21 @@ template struct asio_standalone_standard_executor { - asio_standalone_standard_executor(Executor executor) - noexcept(std::is_nothrow_move_constructible_v) - : executor_(std::move(executor)) + asio_standalone_standard_executor(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) { } + asio_standalone_standard_executor(asio_standalone_standard_executor && rhs) - noexcept(std::is_nothrow_move_constructible_v) - : executor_(std::move(rhs.executor_)) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) { } + asio_standalone_standard_executor( const asio_standalone_standard_executor & rhs) - noexcept(std::is_nothrow_copy_constructible_v) + noexcept(std::is_nothrow_copy_constructible_v) : executor_(rhs.executor_) { } @@ -235,9 +322,10 @@ struct asio_standalone_standard_executor auto & ec = ::asio::query(executor_, ::asio::execution::context); return ::asio::use_service< detail::asio_context_service<::asio::execution_context> - >(ec); + >(ec); } + /** @brief Notifies that work has started. */ void on_work_started() const noexcept { auto & ec = ::asio::query(executor_, ::asio::execution::context); @@ -246,6 +334,7 @@ struct asio_standalone_standard_executor >(ec).work_started(executor_); } + /** @brief Notifies that work has finished. */ void on_work_finished() const noexcept { auto & ec = ::asio::query(executor_, ::asio::execution::context); @@ -254,7 +343,10 @@ struct asio_standalone_standard_executor >(ec).work_finished(); } - + /** @brief Dispatches a continuation for execution. + * @param c The continuation to dispatch + * @return A noop coroutine handle + */ std::coroutine_handle<> dispatch(continuation & c) const { ::asio::prefer( @@ -268,6 +360,9 @@ struct asio_standalone_standard_executor return std::noop_coroutine(); } + /** @brief Posts a continuation for deferred execution. + * @param c The continuation to post + */ void post(continuation & c) const { ::asio::prefer( @@ -280,13 +375,17 @@ struct asio_standalone_standard_executor ) ).execute(detail::asio_coroutine_unique_handle(c.h)); } + + /** @brief Equality comparison. */ bool operator==( - const asio_standalone_standard_executor & rhs) const noexcept - { + const asio_standalone_standard_executor & rhs) const noexcept + { return executor_ == rhs.executor_; } + + /** @brief Inequality comparison. */ bool operator!=( - const asio_standalone_standard_executor & rhs) const noexcept + const asio_standalone_standard_executor & rhs) const noexcept { return executor_ != rhs.executor_; } @@ -296,7 +395,19 @@ struct asio_standalone_standard_executor }; -template @@ -306,7 +417,19 @@ auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) return asio_spawn(exec, std::forward(runnable))(std::move(token)); } -template @@ -318,14 +441,23 @@ auto asio_spawn(Context & ctx, Runnable && runnable, Token token) (std::move(token)); } +/** @} */ // end of asio group + } +/** @brief Standalone Asio async_result specialization for as_io_awaitable_t. + * + * This specialization enables `as_io_awaitable` to be used as a completion + * token with any standalone Asio async operation. + * + * @tparam Ts The completion signature argument types + */ template struct asio::async_result : boost::capy::detail::async_result_impl < - ::asio::cancellation_signal, - ::asio::cancellation_type, + ::asio::cancellation_signal, + ::asio::cancellation_type, Ts... > { @@ -335,8 +467,6 @@ struct asio::async_result namespace boost::capy::detail { - - struct boost_asio_standalone_init; template @@ -381,13 +511,13 @@ struct boost_asio_standalone_promise_type_allocator_base template -struct boost_asio_standalone_init_promise_type +struct boost_asio_standalone_init_promise_type : boost_asio_standalone_promise_type_allocator_base< ::asio::associated_allocator_t > { using args_type = completion_tuple_for_io_runnable; - + boost_asio_standalone_init_promise_type( boost_asio_standalone_init &, Handler & h, @@ -536,9 +666,9 @@ struct boost_asio_standalone_init_promise_type struct boost_asio_standalone_init { - template + template void operator()( - Handler , + Handler , Ex, Runnable runnable) { @@ -547,25 +677,25 @@ struct boost_asio_standalone_init } }; -template +template requires ::asio::completion_token_for< - Token, + Token, completion_signature_for_io_runnable > struct initialize_asio_standalone_spawn_helper { template - static auto init(Executor ex, Runnable r, Token && tk) + static auto init(Executor ex, Runnable r, Token && tk) -> decltype(::asio::async_initiate< - Token, + Token, completion_signature_for_io_runnable>( boost_asio_standalone_init{}, tk, std::move(ex), std::move(r) )) { return ::asio::async_initiate< - Token, + Token, completion_signature_for_io_runnable>( boost_asio_standalone_init{}, tk, std::move(ex), std::move(r) @@ -578,16 +708,16 @@ struct initialize_asio_standalone_spawn_helper template struct std::coroutine_traits< - void, - boost::capy::detail::boost_asio_standalone_init&, - Handler, - Executor, + void, + boost::capy::detail::boost_asio_standalone_init&, + Handler, + Executor, Runnable> { - using promise_type + using promise_type = boost::capy::detail::boost_asio_standalone_init_promise_type< - Handler, - Executor, + Handler, + Executor, Runnable>; }; From 02744973cd7fabbabe84bf577d9e229b0ccdd6df Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 07:25:59 +0800 Subject: [PATCH 13/29] clang warning fixes --- include/boost/capy/asio/boost.hpp | 42 +++++++++++++++----------- include/boost/capy/asio/standalone.hpp | 39 ++++++++++++++---------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 4f106ea4..1669fe6e 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -104,16 +104,18 @@ boost::asio::execution_context& */ template constexpr boost::asio::execution::blocking_t - query(const asio_executor_adapter & exec, + query(const asio_executor_adapter &, boost::asio::execution::blocking_t) noexcept { - switch (Bits & exec.blocking_mask) + using ex = asio_executor_adapter; + + switch (Bits & ex::blocking_mask) { - case exec.blocking_never: + case ex::blocking_never: return boost::asio::execution::blocking.never; - case exec.blocking_always: + case ex::blocking_always: return boost::asio::execution::blocking.always; - case exec.blocking_possibly: + case ex::blocking_possibly: return boost::asio::execution::blocking.possibly; default: return {}; } @@ -132,8 +134,9 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::possibly_t) { - constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; - return asio_executor_adapter(exec); + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_possibly; + return asio_executor_adapter(exec); } /** @brief Requires blocking.never property. @@ -144,8 +147,9 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::never_t) { - constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_never; - return asio_executor_adapter(exec); + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_never; + return asio_executor_adapter(exec); } /** @brief Requires blocking.always property. @@ -156,8 +160,9 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::blocking_t::always_t) { - constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_always; - return asio_executor_adapter(exec); + using ex = asio_executor_adapter; +constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_always; + return asio_executor_adapter(exec); } /** @brief Queries the outstanding_work property. @@ -166,14 +171,15 @@ constexpr auto */ template static constexpr boost::asio::execution::outstanding_work_t query( - const asio_executor_adapter & exec, + const asio_executor_adapter &, boost::asio::execution::outstanding_work_t) noexcept { - switch (Bits & exec.work_mask) + using ex = asio_executor_adapter; + switch (Bits & ex::work_mask) { - case exec.work_tracked: + case ex::work_tracked: return boost::asio::execution::outstanding_work.tracked; - case exec.work_untracked: + case ex::work_untracked: return boost::asio::execution::outstanding_work.untracked; default: return {}; } @@ -187,7 +193,8 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::outstanding_work_t::tracked_t) { - constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::work_mask) | ex::work_tracked; return asio_executor_adapter(exec); } @@ -199,7 +206,8 @@ constexpr auto require(const asio_executor_adapter & exec, boost::asio::execution::outstanding_work_t::untracked_t) { - constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_untracked; + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::work_mask) | ex::work_untracked; return asio_executor_adapter(exec); } diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index ff68797c..37502aaf 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -100,14 +100,15 @@ ::asio::execution_context& */ template constexpr ::asio::execution::blocking_t - query(const asio_executor_adapter & exec, + query(const asio_executor_adapter &, ::asio::execution::blocking_t) noexcept { - switch (Bits & exec.blocking_mask) + using ex = asio_executor_adapter; + switch (Bits & ex::blocking_mask) { - case exec.blocking_never: return ::asio::execution::blocking.never; - case exec.blocking_always: return ::asio::execution::blocking.always; - case exec.blocking_possibly: return ::asio::execution::blocking.possibly; + case ex::blocking_never: return ::asio::execution::blocking.never; + case ex::blocking_always: return ::asio::execution::blocking.always; + case ex::blocking_possibly: return ::asio::execution::blocking.possibly; default: return {}; } } @@ -125,8 +126,9 @@ constexpr auto require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::possibly_t) { - constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_possibly; - return asio_executor_adapter(exec); + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_possibly; + return asio_executor_adapter(exec); } /** @brief Requires blocking.never property. @@ -137,8 +139,9 @@ constexpr auto require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::never_t) { - constexpr int nb = (Bits & ~exec.blocking_mask) | exec.blocking_never; - return asio_executor_adapter(exec); + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_never; + return asio_executor_adapter(exec); } /** @brief Requires blocking.always property. @@ -149,7 +152,8 @@ constexpr auto require(const asio_executor_adapter & exec, ::asio::execution::blocking_t::always_t) { - constexpr int new_bits = (Bits & ~exec.blocking_mask) | exec.blocking_always; + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::blocking_mask) | ex::blocking_always; return asio_executor_adapter(exec); } @@ -159,14 +163,15 @@ constexpr auto */ template static constexpr ::asio::execution::outstanding_work_t query( - const asio_executor_adapter & exec, + const asio_executor_adapter &, ::asio::execution::outstanding_work_t) noexcept { - switch (Bits & exec.work_mask) + using ex = asio_executor_adapter; + switch (Bits & ex::work_mask) { - case exec.work_tracked: + case ex::work_tracked: return ::asio::execution::outstanding_work.tracked; - case exec.work_untracked: + case ex::work_untracked: return ::asio::execution::outstanding_work.untracked; default: return {}; } @@ -180,7 +185,8 @@ constexpr auto require(const asio_executor_adapter & exec, ::asio::execution::outstanding_work_t::tracked_t) { - constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_tracked; + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::work_mask) | ex::work_tracked; return asio_executor_adapter(exec); } @@ -192,7 +198,8 @@ constexpr auto require(const asio_executor_adapter & exec, ::asio::execution::outstanding_work_t::untracked_t) { - constexpr int new_bits = (Bits & ~exec.work_mask) | exec.work_untracked; + using ex = asio_executor_adapter; + constexpr int new_bits = (Bits & ~ex::work_mask) | ex::work_untracked; return asio_executor_adapter(exec); } From ae9b1b82575c9b92e2ce3ae9abe9f73f7f65a8e1 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 07:29:51 +0800 Subject: [PATCH 14/29] make_continuation tries to avoid warnings --- include/boost/capy/asio/detail/continuation.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/capy/asio/detail/continuation.hpp b/include/boost/capy/asio/detail/continuation.hpp index 79e78698..72e38c03 100644 --- a/include/boost/capy/asio/detail/continuation.hpp +++ b/include/boost/capy/asio/detail/continuation.hpp @@ -122,9 +122,10 @@ continuation & make_continuation( Function && func, Allocator && alloc) { - return detail::make_continuation_helper( + continuation & c = detail::make_continuation_helper( std::forward(func), std::forward(alloc)).cont; + return c; } } From da157b3b70ed7b0edf4e0a8000a9550b5fb9bd99 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 15:35:17 +0800 Subject: [PATCH 15/29] execution_context is forward declared as class --- include/boost/capy/asio/detail/fwd.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/capy/asio/detail/fwd.hpp b/include/boost/capy/asio/detail/fwd.hpp index e778a8f0..2ff246c4 100644 --- a/include/boost/capy/asio/detail/fwd.hpp +++ b/include/boost/capy/asio/detail/fwd.hpp @@ -32,7 +32,7 @@ struct query_result; namespace asio { -struct execution_context; +class execution_context; namespace execution::detail { From 7fe28fc500082b222db7576a4d5fc095ecbf9560 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 22:07:18 +0800 Subject: [PATCH 16/29] fixed relevant code-rabbit issues --- include/boost/capy/asio/boost.hpp | 19 ++++++++++++------- include/boost/capy/asio/standalone.hpp | 15 +++++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 1669fe6e..d90dd432 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -276,28 +276,34 @@ struct asio_work_tracker_service : boost::asio::execution_context::service alignas(tracked_executor) char buffer[sizeof(tracked_executor) ]; - std::atomic_size_t work = 0u; + + std::mutex mutex; + std::size_t work = 0u; void shutdown() { - if (work.exchange(0) > 0u) + std::lock_guard _(mutex); + if (std::exchange(work, 0) > 0u) reinterpret_cast(buffer)->~tracked_executor(); } void work_started(const Executor & exec) { - if (work.fetch_add(1u) == 0u) + std::lock_guard _(mutex); + if (work ++ == 0u) new (buffer) tracked_executor( - boost::asio::prefer(exec, + boost::asio::prefer(exec, boost::asio::execution::outstanding_work.tracked)); } - void work_finished() + void work_finished() { - if (work.fetch_sub(1u) == 1u) + std::lock_guard _(mutex); + if (--work == 0u) reinterpret_cast(buffer)->~tracked_executor(); } + }; @@ -602,7 +608,6 @@ struct boost_asio_init_promise_type }, args_); - asio_executor_adapter aex(ex); auto exec = boost::asio::get_associated_immediate_executor(handler, ex_); boost::asio::dispatch(exec, std::move(handler)); diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 37502aaf..9ee2f5b0 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -268,19 +268,22 @@ struct asio_standalone_work_tracker_service : >::type; alignas(tracked_executor) char buffer[sizeof(tracked_executor) ]; - - std::atomic_size_t work = 0u; + + std::mutex mutex; + std::size_t work = 0u; void shutdown() { - if (work.exchange(0) > 0u) + std::lock_guard _(mutex); + if (std::exchange(work, 0) > 0u) reinterpret_cast(buffer)->~tracked_executor(); } void work_started(const Executor & exec) { - if (work.fetch_add(1u) == 0u) + std::lock_guard _(mutex); + if (work ++ == 0u) new (buffer) tracked_executor( ::asio::prefer(exec, ::asio::execution::outstanding_work.tracked)); @@ -288,7 +291,8 @@ struct asio_standalone_work_tracker_service : void work_finished() { - if (work.fetch_sub(1u) == 1u) + std::lock_guard _(mutex); + if (--work == 0u) reinterpret_cast(buffer)->~tracked_executor(); } }; @@ -565,7 +569,6 @@ struct boost_asio_standalone_init_promise_type }, args_); - asio_executor_adapter aex(ex); auto exec = ::asio::get_associated_immediate_executor(handler, ex_); ::asio::dispatch(exec, std::move(handler)); } From 1c0c04e2069b0ffcd04c39b6ea33166d3ef69a3e Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 05:48:07 +0800 Subject: [PATCH 17/29] Asio's steady_timer can use a capy executor --- test/unit/asio.cpp | 24 ++++++++++++++++++++++++ test/unit/asio_standalone.cpp | 23 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp index 83125e4b..ca8ebf43 100644 --- a/test/unit/asio.cpp +++ b/test/unit/asio.cpp @@ -8,6 +8,8 @@ // +#include +#include #if __has_include() #include @@ -17,6 +19,7 @@ #include #include #include +#include #include @@ -141,6 +144,26 @@ struct boost_asio_test BOOST_TEST(done); BOOST_TEST(dispatch_count == 1); } + + void testTimer() + { + int dispatch_count = 0; + test_executor te{dispatch_count}; + boost::capy::asio_executor_adapter wrapped_te{te}; + + boost::asio::steady_timer t{wrapped_te}; + t.expires_after(std::chrono::milliseconds(1)); + + bool done = false; + t.async_wait( + [&](auto ec) + { + done = true; + }); + + while (!done) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } void run() { @@ -149,6 +172,7 @@ struct boost_asio_test testFromAnyIOExecutor(); testAsIoAwaitable(); testAsioSpawn(); + testTimer(); } }; diff --git a/test/unit/asio_standalone.cpp b/test/unit/asio_standalone.cpp index 58c45a3d..5deb3cfb 100644 --- a/test/unit/asio_standalone.cpp +++ b/test/unit/asio_standalone.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -139,6 +140,27 @@ struct asio_standalone_test BOOST_TEST(done); BOOST_TEST(dispatch_count == 1); } + + + void testTimer() + { + int dispatch_count = 0; + test_executor te{dispatch_count}; + boost::capy::asio_executor_adapter wrapped_te{te}; + + ::asio::steady_timer t{wrapped_te}; + t.expires_after(std::chrono::milliseconds(1)); + + bool done = false; + t.async_wait( + [&](auto ec) + { + done = true; + }); + + while (!done) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } void run() { @@ -147,6 +169,7 @@ struct asio_standalone_test testFromAnyIOExecutor(); testAsIoAwaitable(); testAsioSpawn(); + testTimer(); } }; From 93a22b8c48efb192128b56f73e5c90988a922b2d Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 05:59:22 +0800 Subject: [PATCH 18/29] Immediate completion support --- include/boost/capy/asio/detail/completion_handler.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index d2ab43cb..3032c6f8 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -127,7 +127,12 @@ struct asio_coroutine_completion_handler void operator()(Args ... args) { result.emplace(std::forward(args)...); - std::move(handle)(); + + if (completed_immediately != nullptr + && *completed_immediately == completed_immediately_t::maybe) + *completed_immediately = completed_immediately_t::yes; + else + std::move(handle)(); } }; From 0d305b92a424f5024dc8e97d05053caff13543dd Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:09:30 +0800 Subject: [PATCH 19/29] noexcept spec for asio_spawn is clearer --- doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc index df8c9c99..80cf2db9 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc @@ -202,7 +202,8 @@ auto [ep, result] = future.get(); === Completion Signature -The completion signature depends on the coroutine's return type and exception specification: +The completion signature depends on the coroutine's return type +and the exception specification of `await_resume`: [cols="2,2"] |=== From 022b5e70d60007d7b7faa5a0df507d826077e69e Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:10:26 +0800 Subject: [PATCH 20/29] decay_t is defensively deployed --- include/boost/capy/asio/detail/completion_handler.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index 3032c6f8..858870ec 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace boost::capy::detail { @@ -164,7 +165,7 @@ struct async_result_impl { completed_immediately = completed_immediately_t::initiating; stopper.emplace(env->stop_token, signal); - using slot_t = decltype(CancellationSignal().slot()); + using slot_t = std::decay_t; capy::detail::asio_coroutine_completion_handler ch( h, result_, env, signal.slot(), From 367d2c556e35f4aa132d99204bae772620f863a5 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:45:21 +0800 Subject: [PATCH 21/29] CI fixes --- include/boost/capy/asio/detail/completion_handler.hpp | 6 ++++-- include/boost/capy/asio/detail/fwd.hpp | 2 +- test/unit/asio.cpp | 1 + test/unit/asio_standalone.cpp | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index 858870ec..71642808 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -141,7 +141,6 @@ struct asio_coroutine_completion_handler template struct async_result_impl { - template struct awaitable_t { @@ -185,7 +184,10 @@ struct async_result_impl return completed_immediately != completed_immediately_t::yes; } - std::tuple await_resume() {return std::move(*result_); } + auto await_resume() + { + return std::move(*result_); + } awaitable_t(Initiation init, std::tuple args) diff --git a/include/boost/capy/asio/detail/fwd.hpp b/include/boost/capy/asio/detail/fwd.hpp index 2ff246c4..bbb843a3 100644 --- a/include/boost/capy/asio/detail/fwd.hpp +++ b/include/boost/capy/asio/detail/fwd.hpp @@ -13,7 +13,7 @@ namespace boost::asio { -struct execution_context; +class execution_context; namespace execution::detail { diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp index ca8ebf43..0386ac7f 100644 --- a/test/unit/asio.cpp +++ b/test/unit/asio.cpp @@ -158,6 +158,7 @@ struct boost_asio_test t.async_wait( [&](auto ec) { + BOOST_TEST(!ec); done = true; }); diff --git a/test/unit/asio_standalone.cpp b/test/unit/asio_standalone.cpp index 5deb3cfb..831490b6 100644 --- a/test/unit/asio_standalone.cpp +++ b/test/unit/asio_standalone.cpp @@ -155,6 +155,7 @@ struct asio_standalone_test t.async_wait( [&](auto ec) { + BOOST_TEST(!ec); done = true; }); From 7ac5a96b7221dee49669b01259fdf302bad4ac3a Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:53:49 +0800 Subject: [PATCH 22/29] init_promise_type has return_void --- include/boost/capy/asio/boost.hpp | 2 +- include/boost/capy/asio/standalone.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index d90dd432..e8310c02 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -580,7 +580,7 @@ struct boost_asio_init_promise_type void get_return_object() {} void unhandled_exception() {throw;} - void return_value() {} + void return_void() {} std::suspend_never initial_suspend() noexcept {return {};} std::suspend_never final_suspend() noexcept {return {};} diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 9ee2f5b0..df47302f 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -541,7 +541,7 @@ struct boost_asio_standalone_init_promise_type void get_return_object() {} void unhandled_exception() {throw;} - void return_value() {} + void return_void() {} std::suspend_never initial_suspend() noexcept {return {};} std::suspend_never final_suspend() noexcept {return {};} From 88a15399732f029f5049f07c136549ca9aac1b22 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 08:01:20 +0800 Subject: [PATCH 23/29] restructure contination& coro --- .../boost/capy/asio/detail/continuation.hpp | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/include/boost/capy/asio/detail/continuation.hpp b/include/boost/capy/asio/detail/continuation.hpp index 72e38c03..fb99f2c0 100644 --- a/include/boost/capy/asio/detail/continuation.hpp +++ b/include/boost/capy/asio/detail/continuation.hpp @@ -66,7 +66,24 @@ template struct continuation_handle_promise_type : continuation_handle_promise_base_ { - std::suspend_always initial_suspend() const noexcept {return {};} + + struct initial_aw_t + { + bool await_ready() const {return false;} + void await_suspend( + std::coroutine_handle> h) + { + auto & c = h.promise().cont; + c.h = h; + c.next = nullptr; + } + void await_resume() {} + }; + + initial_aw_t initial_suspend() const noexcept + { + return initial_aw_t{}; + } std::suspend_never final_suspend() const noexcept {return {};} template @@ -89,30 +106,29 @@ struct continuation_handle_promise_type return yielder{std::move(func)}; } + void unhandled_exception() { throw; } + void return_void() {} + continuation cont; - void unhandled_exception() { throw; } - continuation & get_return_object() + struct helper + { + continuation * cont; + using promise_type = continuation_handle_promise_type; + }; + + helper get_return_object() { - using handle_t = std::coroutine_handle; - cont.h = handle_t::from_promise(*this); - cont.next = nullptr; - return cont; + return helper{&cont}; } }; -template -struct continuation_helper -{ - capy::continuation &cont; - continuation_helper(continuation & cont) noexcept : cont(cont) {} - using promise_type = continuation_handle_promise_type; -}; template Function, typename Allocator> -continuation_helper make_continuation_helper( +auto make_continuation_helper( Function func, Allocator) + -> continuation_handle_promise_type::helper { co_yield func; } @@ -122,10 +138,10 @@ continuation & make_continuation( Function && func, Allocator && alloc) { - continuation & c = detail::make_continuation_helper( + continuation * c = detail::make_continuation_helper( std::forward(func), std::forward(alloc)).cont; - return c; + return *c; } } From a28f940195996d1040ba78fc34542ebfb37f2e0c Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 08:22:09 +0800 Subject: [PATCH 24/29] fix name shadowing --- include/boost/capy/asio/boost.hpp | 6 +++--- include/boost/capy/asio/standalone.hpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index e8310c02..30fd5280 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -600,7 +600,7 @@ struct boost_asio_init_promise_type asio_executor_adapter ex_ = std::move(ex); h.destroy(); - auto handler = + auto handler_ = std::apply( [&](auto ... args) { @@ -609,8 +609,8 @@ struct boost_asio_init_promise_type args_); auto exec = - boost::asio::get_associated_immediate_executor(handler, ex_); - boost::asio::dispatch(exec, std::move(handler)); + boost::asio::get_associated_immediate_executor(handler_, ex_); + boost::asio::dispatch(exec, std::move(handler_)); } void await_resume() const {} }; diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index df47302f..0c57c4a0 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -561,7 +561,7 @@ struct boost_asio_standalone_init_promise_type asio_executor_adapter ex_ = std::move(ex); h.destroy(); - auto handler = + auto handler_ = std::apply( [&](auto ... args) { @@ -569,8 +569,8 @@ struct boost_asio_standalone_init_promise_type }, args_); - auto exec = ::asio::get_associated_immediate_executor(handler, ex_); - ::asio::dispatch(exec, std::move(handler)); + auto exec = ::asio::get_associated_immediate_executor(handler_, ex_); + ::asio::dispatch(exec, std::move(handler_)); } void await_resume() const {} }; From eccdb662db194e5fae1a17b418afaf4098caeaa2 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Sat, 18 Apr 2026 07:09:12 +0800 Subject: [PATCH 25/29] asio integration uses io_result --- .../4.coroutines/4i.asio-integration.adoc | 16 +++++++++++---- .../capy/asio/detail/completion_handler.hpp | 20 ++++++++++++++++++- .../capy/asio/detail/completion_traits.hpp | 9 +++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc index 80cf2db9..165fda31 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc @@ -82,20 +82,28 @@ The `as_io_awaitable` token transforms the operation into an awaitable that: === Completion Signatures -The result type depends on the Asio operation's completion signature: +The result type depends on the Asio operation's completion signature. +When the signature begins with `error_code`, the result is an `io_result` +containing the error code plus any additional values. See xref:../3.error-handling/3b.io-result.adoc[io_result] for details. [cols="1,1"] |=== | Asio Signature | co_await Result | `void(error_code, std::size_t)` -| `std::tuple` +| `io_result` | `void(error_code)` -| `std::tuple` +| `io_result<>` + +| `void(std::exception_ptr, std::size_t)` +| `std::size_t` + +| `void(std::exception_ptr)` +| `void` | `void()` -| `std::tuple<>` +| `void` |=== Use structured bindings for clean syntax: diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index 71642808..21b42bfa 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -137,6 +138,22 @@ struct asio_coroutine_completion_handler } }; +template +struct async_result_impl_result_tuple +{ + using type = std::tuple; +}; + + +template + requires std::constructible_from, T0, Ts...> +struct async_result_impl_result_tuple +{ + using type = io_result; +}; + + + template struct async_result_impl @@ -201,7 +218,8 @@ struct async_result_impl , result_(std::move(rhs.result_)) {} private: Initiation init_; - std::tuple args_; + // if Args<0> == error_code this needs to be an io_result. + typename async_result_impl_result_tuple::type args_; std::optional> result_; }; diff --git a/include/boost/capy/asio/detail/completion_traits.hpp b/include/boost/capy/asio/detail/completion_traits.hpp index b98d428b..037271e4 100644 --- a/include/boost/capy/asio/detail/completion_traits.hpp +++ b/include/boost/capy/asio/detail/completion_traits.hpp @@ -11,6 +11,7 @@ #define BOOST_CAPY_ASIO_DETAIL_COMPLETION_TRAITS_HPP #include +#include #include #include @@ -35,6 +36,14 @@ struct completion_traits }; +template +struct completion_traits, true> +{ + using signature_type = void(Ts...); + using result_type = io_result; +}; + + template<> struct completion_traits { From 27f34ec5b98b54210f17e53e8b98bd117a408683 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Sat, 18 Apr 2026 08:13:48 +0800 Subject: [PATCH 26/29] Spawn uses IoAwaitable not IoRunnable --- .../4.coroutines/4i.asio-integration.adoc | 2 +- include/boost/capy/asio/boost.hpp | 74 +++++++++---------- .../capy/asio/detail/completion_traits.hpp | 18 ++--- include/boost/capy/asio/spawn.hpp | 64 ++++++++-------- include/boost/capy/asio/standalone.hpp | 72 +++++++++--------- 5 files changed, 113 insertions(+), 117 deletions(-) diff --git a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc index 165fda31..27b11b07 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc @@ -84,7 +84,7 @@ The `as_io_awaitable` token transforms the operation into an awaitable that: The result type depends on the Asio operation's completion signature. When the signature begins with `error_code`, the result is an `io_result` -containing the error code plus any additional values. See xref:../3.error-handling/3b.io-result.adoc[io_result] for details. +which bundles the error code with any additional values and supports structured bindings. [cols="1,1"] |=== diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 30fd5280..58d3f634 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -47,7 +47,7 @@ */ #include -#include +#include #include #include #include @@ -457,24 +457,24 @@ struct asio_boost_standard_executor /** @brief Spawns a capy coroutine with a Boost.Asio completion token (executor overload). * * Convenience overload that combines the two-step spawn process into one call. - * Equivalent to `asio_spawn(exec, runnable)(token)`. + * Equivalent to `asio_spawn(exec, awaitable)(token)`. * * @tparam ExecutorType The executor type - * @tparam Runnable The coroutine type + * @tparam Awaitable The coroutine type * @tparam Token A Boost.Asio completion token * @param exec The executor to run on - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn * @param token The completion token * @return Depends on the token type */ template + detail::completion_signature_for_io_awaitable > Token> -auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +auto asio_spawn(ExecutorType exec, Awaitable && awaitable, Token token) { - return asio_spawn(exec, std::forward(runnable))(std::move(token)); + return asio_spawn(exec, std::forward(awaitable))(std::move(token)); } /** @brief Spawns a capy coroutine with a Boost.Asio completion token (context overload). @@ -482,21 +482,21 @@ auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) * Convenience overload that extracts the executor from a context. * * @tparam Context The execution context type - * @tparam Runnable The coroutine type + * @tparam Awaitable The coroutine type * @tparam Token A Boost.Asio completion token * @param ctx The execution context - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn * @param token The completion token * @return Depends on the token type */ template + detail::completion_signature_for_io_awaitable > Token> -auto asio_spawn(Context & ctx, Runnable && runnable, Token token) +auto asio_spawn(Context & ctx, Awaitable && awaitable, Token token) { - return asio_spawn(ctx.get_executor(), std::forward(runnable))(std::move(token)); + return asio_spawn(ctx.get_executor(), std::forward(awaitable))(std::move(token)); } /** @} */ // end of asio group @@ -523,10 +523,10 @@ struct boost_asio_init; template struct boost_asio_promise_type_allocator_base { - template + template void * operator new (std::size_t n, boost_asio_init &, Handler & handler, - Ex &, Runnable &) + Ex &, Awaitable &) { using allocator_type = std::allocator_traits ::template rebind_alloc; @@ -561,18 +561,18 @@ struct boost_asio_promise_type_allocator_base }; -template +template struct boost_asio_init_promise_type : boost_asio_promise_type_allocator_base< boost::asio::associated_allocator_t> { - using args_type = completion_tuple_for_io_runnable; + using args_type = completion_tuple_for_io_awaitable; boost_asio_init_promise_type( boost_asio_init &, Handler & h, Ex & exec, - Runnable &) + Awaitable &) : handler(h), ex(exec) {} Handler & handler; @@ -622,7 +622,7 @@ struct boost_asio_init_promise_type struct wrapper { - Runnable r; + Awaitable r; const Ex &ex; io_env env; std::stop_source stop_src; @@ -630,7 +630,7 @@ struct boost_asio_init_promise_type continuation c; - wrapper(Runnable && r, const Ex &ex) + wrapper(Awaitable && r, const Ex &ex) : r(std::move(r)), ex(ex) { } @@ -643,7 +643,6 @@ struct boost_asio_init_promise_type // always post in auto h = r.handle(); auto & p = h.promise(); - p.set_continuation(tr); env.executor = ex; env.stop_token = stop_src.get_token(); @@ -661,12 +660,11 @@ struct boost_asio_init_promise_type env.frame_allocator = get_current_frame_allocator(); - p.set_environment(&env); - c.h = h; + c.h = r.await_suspend(tr, &env); return ex.dispatch(c); } - completion_tuple_for_io_runnable await_resume() + completion_tuple_for_io_awaitable await_resume() { if (cancel_slot.is_connected()) cancel_slot.clear(); @@ -707,7 +705,7 @@ struct boost_asio_init_promise_type } }; - wrapper await_transform(Runnable & r) + wrapper await_transform(Awaitable & r) { return wrapper{std::move(r), ex}; } @@ -716,37 +714,37 @@ struct boost_asio_init_promise_type struct boost_asio_init { - template + template void operator()( Handler , Ex, - Runnable runnable) + Awaitable awaitable) { - auto res = co_await runnable; + auto res = co_await awaitable; co_yield std::move(res); } }; -template +template requires boost::asio::completion_token_for< Token, - completion_signature_for_io_runnable + completion_signature_for_io_awaitable > -struct initialize_asio_spawn_helper +struct initialize_asio_spawn_helper { template - static auto init(Executor ex, Runnable r, Token && tk) + static auto init(Executor ex, Awaitable r, Token && tk) -> decltype( boost::asio::async_initiate< Token, - completion_signature_for_io_runnable>( + completion_signature_for_io_awaitable>( boost_asio_init{}, tk, std::move(ex), std::move(r) )) { return boost::asio::async_initiate< Token, - completion_signature_for_io_runnable>( + completion_signature_for_io_awaitable>( boost_asio_init{}, tk, std::move(ex), std::move(r) ); @@ -757,18 +755,18 @@ struct initialize_asio_spawn_helper } -template +template struct std::coroutine_traits + Awaitable> { using promise_type = boost::capy::detail::boost_asio_init_promise_type< Handler, Executor, - Runnable>; + Awaitable>; }; #endif //BOOST_CAPY_ASIO_BOOST_HPP diff --git a/include/boost/capy/asio/detail/completion_traits.hpp b/include/boost/capy/asio/detail/completion_traits.hpp index 037271e4..dae4bf3f 100644 --- a/include/boost/capy/asio/detail/completion_traits.hpp +++ b/include/boost/capy/asio/detail/completion_traits.hpp @@ -10,7 +10,7 @@ #ifndef BOOST_CAPY_ASIO_DETAIL_COMPLETION_TRAITS_HPP #define BOOST_CAPY_ASIO_DETAIL_COMPLETION_TRAITS_HPP -#include +#include #include #include @@ -58,18 +58,18 @@ struct completion_traits using result_type = std::tuple<>; }; -template -using completion_signature_for_io_runnable +template +using completion_signature_for_io_awaitable = typename completion_traits< - decltype(std::declval().await_resume()), - noexcept(std::declval().await_resume()) + decltype(std::declval().await_resume()), + noexcept(std::declval().await_resume()) >::signature_type; -template -using completion_tuple_for_io_runnable +template +using completion_tuple_for_io_awaitable = typename completion_traits< - decltype(std::declval().await_resume()), - noexcept(std::declval().await_resume()) + decltype(std::declval().await_resume()), + noexcept(std::declval().await_resume()) >::result_type; diff --git a/include/boost/capy/asio/spawn.hpp b/include/boost/capy/asio/spawn.hpp index cadb66cf..6dbed606 100644 --- a/include/boost/capy/asio/spawn.hpp +++ b/include/boost/capy/asio/spawn.hpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include namespace boost::capy @@ -34,7 +34,7 @@ namespace detail * * Specialized in `boost.hpp` for actual Boost.Asio completion tokens. */ -template +template struct initialize_asio_spawn_helper; /** @brief Concept for valid Boost.Asio spawn completion tokens. @@ -43,11 +43,11 @@ struct initialize_asio_spawn_helper; * A token satisfies this concept if `initialize_asio_spawn_helper` can * initialize the spawn operation with it. */ -template +template concept asio_spawn_token = - requires (Token && tk, Executor ex, Runnable rn) + requires (Token && tk, Executor ex, Awaitable rn) { - initialize_asio_spawn_helper:: + initialize_asio_spawn_helper:: init(std::move(ex), std::move(rn), std::forward(tk)); }; @@ -56,7 +56,7 @@ concept asio_spawn_token = * * Specialized in `standalone.hpp` for actual standalone Asio completion tokens. */ -template +template struct initialize_asio_standalone_spawn_helper; /** @brief Concept for valid standalone Asio spawn completion tokens. @@ -65,11 +65,11 @@ struct initialize_asio_standalone_spawn_helper; * A token satisfies this concept if `initialize_asio_standalone_spawn_helper` * can initialize the spawn operation with it. */ -template +template concept asio_standalone_spawn_token = - requires (Token && tk, Executor ex, Runnable rn) + requires (Token && tk, Executor ex, Awaitable rn) { - initialize_asio_standalone_spawn_helper:: + initialize_asio_standalone_spawn_helper:: init(std::move(ex), std::move(rn), std::forward(tk)); }; @@ -79,11 +79,11 @@ concept asio_standalone_spawn_token = /** @brief Deferred spawn operation that can be initiated with a completion token. * * This class represents a spawn operation that has captured an executor and - * runnable but hasn't been initiated yet. Call `operator()` with a completion + * awaitable but hasn't been initiated yet. Call `operator()` with a completion * token to start the operation. * * @tparam Executor The executor type for running the coroutine - * @tparam Runnable The coroutine type (must satisfy `IoRunnable`) + * @tparam Awaitable The coroutine type (must satisfy `IoAwaitable`) * * @par Example * @code @@ -97,15 +97,15 @@ concept asio_standalone_spawn_token = * * @see asio_spawn Factory function to create spawn operations */ -template +template struct asio_spawn_op { /** @brief Constructs the spawn operation. * @param executor The executor to run on - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn */ - asio_spawn_op(Executor executor, Runnable runnable) - : executor_(std::move(executor)), runnable_(std::move(runnable)) + asio_spawn_op(Executor executor, Awaitable awaitable) + : executor_(std::move(executor)), awaitable_(std::move(awaitable)) {} /** @brief Initiates the spawn with a Boost.Asio completion token. @@ -114,12 +114,12 @@ struct asio_spawn_op * @param token The completion token determining how to handle completion * @return Depends on the token (e.g., void for callbacks, awaitable for use_awaitable) */ - template Token> + template Token> auto operator()(Token && token) { - return detail::initialize_asio_spawn_helper::init( + return detail::initialize_asio_spawn_helper::init( std::move(executor_), - std::move(runnable_), + std::move(awaitable_), std::forward(token) ); } @@ -130,20 +130,20 @@ struct asio_spawn_op * @param token The completion token determining how to handle completion * @return Depends on the token */ - template Token> + template Token> auto operator()(Token && token) { - return detail::initialize_asio_standalone_spawn_helper::init + return detail::initialize_asio_standalone_spawn_helper::init ( std::move(executor_), - std::move(runnable_), + std::move(awaitable_), std::forward(token) ); } private: Executor executor_; - Runnable runnable_; + Awaitable awaitable_; }; @@ -153,9 +153,9 @@ struct asio_spawn_op * completion token. The coroutine will run on the specified executor. * * @tparam ExecutorType The executor type (must satisfy `Executor`) - * @tparam Runnable The coroutine type (must satisfy `IoRunnable`) + * @tparam Awaitable The coroutine type (must satisfy `IoAwaitable`) * @param exec The executor to run the coroutine on - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn * @return An `asio_spawn_op` that can be called with a completion token * * @par Completion Signature @@ -181,10 +181,10 @@ struct asio_spawn_op * * @see asio_spawn_op The returned operation type */ -template -auto asio_spawn(ExecutorType exec, Runnable && runnable) +template +auto asio_spawn(ExecutorType exec, Awaitable && awaitable) { - return asio_spawn_op(std::move(exec), std::forward(runnable)); + return asio_spawn_op(std::move(exec), std::forward(awaitable)); } /** @brief Spawns a capy coroutine using a context's executor. @@ -192,9 +192,9 @@ auto asio_spawn(ExecutorType exec, Runnable && runnable) * Convenience overload that extracts the executor from an execution context. * * @tparam Context The execution context type (must satisfy `ExecutionContext`) - * @tparam Runnable The coroutine type (must satisfy `IoRunnable`) + * @tparam Awaitable The coroutine type (must satisfy `IoAwaitable`) * @param ctx The execution context providing the executor - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn * @return An `asio_spawn_op` that can be called with a completion token * * @par Example @@ -204,10 +204,10 @@ auto asio_spawn(ExecutorType exec, Runnable && runnable) * io.run(); * @endcode */ -template -auto asio_spawn(Context & ctx, Runnable && runnable) +template +auto asio_spawn(Context & ctx, Awaitable && awaitable) { - return asio_spawn_op(ctx.get_executor(), std::forward(runnable)); + return asio_spawn_op(ctx.get_executor(), std::forward(awaitable)); } /** @} */ // end of asio group diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 0c57c4a0..7367340f 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -48,7 +48,7 @@ */ #include -#include +#include #include #include #include @@ -411,21 +411,21 @@ struct asio_standalone_standard_executor * Convenience overload that combines the two-step spawn process into one call. * * @tparam ExecutorType The executor type - * @tparam Runnable The coroutine type + * @tparam Awaitable The coroutine type * @tparam Token A standalone Asio completion token * @param exec The executor to run on - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn * @param token The completion token * @return Depends on the token type */ template + detail::completion_signature_for_io_awaitable > Token> -auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) +auto asio_spawn(ExecutorType exec, Awaitable && awaitable, Token token) { - return asio_spawn(exec, std::forward(runnable))(std::move(token)); + return asio_spawn(exec, std::forward(awaitable))(std::move(token)); } /** @brief Spawns a capy coroutine with a standalone Asio completion token (context overload). @@ -433,22 +433,22 @@ auto asio_spawn(ExecutorType exec, Runnable && runnable, Token token) * Convenience overload that extracts the executor from a context. * * @tparam Context The execution context type - * @tparam Runnable The coroutine type + * @tparam Awaitable The coroutine type * @tparam Token A standalone Asio completion token * @param ctx The execution context - * @param runnable The coroutine to spawn + * @param awaitable The coroutine to spawn * @param token The completion token * @return Depends on the token type */ template + detail::completion_signature_for_io_awaitable > Token > -auto asio_spawn(Context & ctx, Runnable && runnable, Token token) +auto asio_spawn(Context & ctx, Awaitable && awaitable, Token token) { - return asio_spawn(ctx.get_executor(), std::forward(runnable)) + return asio_spawn(ctx.get_executor(), std::forward(awaitable)) (std::move(token)); } @@ -483,10 +483,10 @@ struct boost_asio_standalone_init; template struct boost_asio_standalone_promise_type_allocator_base { - template + template void * operator new (std::size_t n, boost_asio_standalone_init &, Handler & handler, - Ex &, Runnable &) + Ex &, Awaitable &) { using allocator_type = std::allocator_traits ::template rebind_alloc; @@ -521,19 +521,19 @@ struct boost_asio_standalone_promise_type_allocator_base }; -template +template struct boost_asio_standalone_init_promise_type : boost_asio_standalone_promise_type_allocator_base< ::asio::associated_allocator_t > { - using args_type = completion_tuple_for_io_runnable; + using args_type = completion_tuple_for_io_awaitable; boost_asio_standalone_init_promise_type( boost_asio_standalone_init &, Handler & h, Ex & exec, - Runnable &) + Awaitable &) : handler(h), ex(exec) {} Handler & handler; @@ -582,13 +582,13 @@ struct boost_asio_standalone_init_promise_type struct wrapper { - Runnable r; + Awaitable r; const Ex &ex; io_env env; std::stop_source stop_src; ::asio::cancellation_slot cancel_slot; - wrapper(Runnable && r, const Ex &ex) + wrapper(Awaitable && r, const Ex &ex) : r(std::move(r)), ex(ex) { } @@ -603,7 +603,6 @@ struct boost_asio_standalone_init_promise_type // always post in auto h = r.handle(); auto & p = h.promise(); - p.set_continuation(tr); env.executor = ex; env.stop_token = stop_src.get_token(); @@ -620,12 +619,11 @@ struct boost_asio_standalone_init_promise_type env.frame_allocator = get_current_frame_allocator(); - p.set_environment(&env); - c.h = h; + c.h = r.await_suspend(tr, &env); return ex.dispatch(c); } - completion_tuple_for_io_runnable await_resume() + completion_tuple_for_io_awaitable await_resume() { if (cancel_slot.is_connected()) cancel_slot.clear(); @@ -666,7 +664,7 @@ struct boost_asio_standalone_init_promise_type } }; - wrapper await_transform(Runnable & r) + wrapper await_transform(Awaitable & r) { return wrapper{std::move(r), ex}; } @@ -676,37 +674,37 @@ struct boost_asio_standalone_init_promise_type struct boost_asio_standalone_init { - template + template void operator()( Handler , Ex, - Runnable runnable) + Awaitable awaitable) { - auto res = co_await runnable; + auto res = co_await awaitable; co_yield std::move(res); } }; -template +template requires ::asio::completion_token_for< Token, - completion_signature_for_io_runnable + completion_signature_for_io_awaitable > -struct initialize_asio_standalone_spawn_helper +struct initialize_asio_standalone_spawn_helper { template - static auto init(Executor ex, Runnable r, Token && tk) + static auto init(Executor ex, Awaitable r, Token && tk) -> decltype(::asio::async_initiate< Token, - completion_signature_for_io_runnable>( + completion_signature_for_io_awaitable>( boost_asio_standalone_init{}, tk, std::move(ex), std::move(r) )) { return ::asio::async_initiate< Token, - completion_signature_for_io_runnable>( + completion_signature_for_io_awaitable>( boost_asio_standalone_init{}, tk, std::move(ex), std::move(r) ); @@ -716,19 +714,19 @@ struct initialize_asio_standalone_spawn_helper } -template +template struct std::coroutine_traits< void, boost::capy::detail::boost_asio_standalone_init&, Handler, Executor, - Runnable> + Awaitable> { using promise_type = boost::capy::detail::boost_asio_standalone_init_promise_type< Handler, Executor, - Runnable>; + Awaitable>; }; #endif // BOOST_CAPY_ASIO_STANDALONE_HPP From fdbd289371762ed65939e7c988444d0e5f4efafc Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 20 Apr 2026 09:21:50 +0800 Subject: [PATCH 27/29] Buffer adoption is supported --- .../4.coroutines/4i.asio-integration.adoc | 126 +++++ include/boost/capy/asio/boost.hpp | 129 +++++- include/boost/capy/asio/buffers.hpp | 437 ++++++++++++++++++ include/boost/capy/asio/standalone.hpp | 145 +++++- include/boost/capy/buffers/asio.hpp | 329 ------------- test/unit/asio.cpp | 42 ++ test/unit/asio_standalone.cpp | 38 ++ test/unit/buffers/asio.cpp | 329 ------------- 8 files changed, 913 insertions(+), 662 deletions(-) create mode 100644 include/boost/capy/asio/buffers.hpp delete mode 100644 include/boost/capy/buffers/asio.hpp delete mode 100644 test/unit/buffers/asio.cpp diff --git a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc index 27b11b07..224c422a 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc @@ -378,12 +378,129 @@ int main() } ---- +== Buffer Sequence Integration + +Capy provides utilities for seamless bidirectional conversion between Asio buffer sequences and capy's native buffer types. This allows: + +* Using capy's `MutableBufferSequence` and `ConstBufferSequence` with Asio I/O operations +* Using Asio buffer sequences with capy's buffer concepts and algorithms + +=== Buffer Types + +Capy provides Asio-compatible buffer wrappers that inherit from capy's native buffer types: + +[cols="1,2"] +|=== +| Type | Description + +| `asio_const_buffer` +| Extends `const_buffer` with implicit conversions to `asio::const_buffer` and `boost::asio::const_buffer` + +| `asio_mutable_buffer` +| Extends `mutable_buffer` with implicit conversions to both mutable and const Asio buffer types +|=== + +These types allow capy buffers to be used directly with Asio operations: + +[source,cpp] +---- +capy::asio_mutable_buffer buf(data, size); + +// Works with Asio operations +co_await socket.async_read_some(buf, capy::as_io_awaitable); +---- + +=== The as_asio_buffer_sequence Function + +The `as_asio_buffer_sequence` function provides bidirectional conversion between Asio and capy buffer sequences. The returned range satisfies both Asio's buffer sequence requirements and capy's `MutableBufferSequence`/`ConstBufferSequence` concepts. + +==== Capy to Asio + +Convert capy buffer sequences for use with Asio operations: + +[source,cpp] +---- +// Single buffers -> array of 1 +auto seq1 = capy::as_asio_buffer_sequence(my_mutable_buffer); +auto seq2 = capy::as_asio_buffer_sequence(my_const_buffer); + +// Capy buffer sequences -> wrapped with transforming range +capy::mutable_buffer_pair buffers = /* ... */; +auto seq3 = capy::as_asio_buffer_sequence(buffers); + +// Use with Asio +co_await socket.async_read_some(seq3, capy::as_io_awaitable); +---- + +==== Asio to Capy + +Convert Asio buffer sequences for use with capy's buffer concepts: + +[source,cpp] +---- +// Asio buffer sequences -> wrapped with transforming range +std::vector asio_buffers = /* ... */; +auto seq = capy::as_asio_buffer_sequence(asio_buffers); + +// Now satisfies capy::MutableBufferSequence +static_assert(capy::MutableBufferSequence); + +// Use with capy buffer algorithms +std::size_t total = capy::buffer_size(seq); +---- + +==== Pass-Through + +Ranges that already contain `asio_const_buffer` or `asio_mutable_buffer` elements are forwarded unchanged: + +[source,cpp] +---- +std::vector already_compatible = /* ... */; +auto seq = capy::as_asio_buffer_sequence(already_compatible); +// Returns the range as-is, no wrapping needed +---- + +=== Example: Scatter/Gather I/O + +Using buffer sequences enables efficient scatter/gather I/O: + +[source,cpp] +---- +#include +#include + +capy::io_task read_message( + boost::asio::ip::tcp::socket& socket) +{ + // Header + body buffers + std::array header; + std::array body; + + capy::mutable_buffer_pair buffers( + capy::mutable_buffer(header.data(), header.size()), + capy::mutable_buffer(body.data(), body.size())); + + // Scatter read into both buffers + auto [ec, n] = co_await socket.async_read_some( + capy::as_asio_buffer_sequence(buffers), + capy::as_io_awaitable); + + if (ec) + throw std::system_error(ec); + + co_return n; +} +---- + == Reference [cols="1,2"] |=== | Header | Description +| `` +| Buffer types, iterator, and `as_asio_buffer_sequence` (included by boost.hpp/standalone.hpp) + | `` | Complete Boost.Asio integration @@ -421,5 +538,14 @@ int main() | `asio_executor_adapter` | Adapt capy executors for Asio + +| `asio_const_buffer` +| Capy const buffer with Asio conversion operators + +| `asio_mutable_buffer` +| Capy mutable buffer with Asio conversion operators + +| `as_asio_buffer_sequence` +| Convert any buffer sequence for Asio compatibility |=== diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 58d3f634..6237743f 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -46,18 +46,21 @@ * @ingroup asio */ -#include #include + +#include +#include #include #include -#include #include +#include #include #include #include #include +#include #include #include #include @@ -67,6 +70,7 @@ #include #include #include +#include namespace boost::capy { @@ -769,5 +773,126 @@ struct std::coroutine_traits; }; + + +namespace boost::capy +{ + + +namespace detail +{ + +template +class asio_boost_buffer_range +{ +public: + using sequence_type = Sequence; + using iterator = asio_buffer_iterator< + decltype(boost::asio::buffer_sequence_begin(std::declval()))>; + using const_iterator = iterator; + + asio_boost_buffer_range() = default; + + explicit asio_boost_buffer_range(Sequence seq) + noexcept(std::is_nothrow_move_constructible_v) + : seq_(std::move(seq)) + { + } + + asio_boost_buffer_range(const asio_boost_buffer_range&) = default; + asio_boost_buffer_range(asio_boost_buffer_range&&) = default; + asio_boost_buffer_range& operator=(const asio_boost_buffer_range&) = default; + asio_boost_buffer_range& operator=(asio_boost_buffer_range&&) = default; + + iterator begin() const + noexcept(noexcept(boost::asio::buffer_sequence_begin(std::declval()))) + { + return iterator(boost::asio::buffer_sequence_begin(seq_)); + } + + iterator end() const + noexcept(noexcept(boost::asio::buffer_sequence_end(std::declval()))) + { + return iterator(boost::asio::buffer_sequence_end(seq_)); + } + + /// Returns the underlying sequence. + const Sequence& base() const noexcept { return seq_; } + +private: + Sequence seq_{}; +}; + +// Deduction guide +template +asio_boost_buffer_range(Sequence) -> asio_boost_buffer_range; + + +asio_mutable_buffer asio_buffer_transformer_t:: + operator()(const boost::asio::mutable_buffer &mb) const noexcept +{ + return {mb.data(), mb.size()}; +} + +asio_const_buffer asio_buffer_transformer_t:: + operator()(const boost::asio::const_buffer &cb) const noexcept +{ + return {cb.data(), cb.size()}; +} + +} + +/** Convert a Boost.Asio buffer sequence for bidirectional Asio/capy compatibility. + + Wraps a Boost.Asio buffer sequence in a transforming range that converts + each buffer element to asio_const_buffer or asio_mutable_buffer. + The returned range satisfies both Boost.Asio's buffer sequence requirements + and capy's buffer sequence concepts. + + This overload uses `boost::asio::buffer_sequence_begin` and + `boost::asio::buffer_sequence_end` for iteration. + + @par Example: Asio to Capy + @code + std::vector asio_bufs = ...; + auto seq = capy::as_asio_buffer_sequence(asio_bufs); + std::size_t total = capy::buffer_size(seq); // Use capy algorithms + @endcode + + @param seq The Boost.Asio buffer sequence to convert + @return A transforming range over the buffer sequence + + @see as_asio_buffer_sequence in buffers.hpp for capy buffer sequences +*/ +template + requires requires (const T & seq) + { + {boost::asio::buffer_sequence_begin(seq)} -> std::bidirectional_iterator; + {boost::asio::buffer_sequence_end(seq)} -> std::bidirectional_iterator; + {*boost::asio::buffer_sequence_begin(seq)} -> std::convertible_to; + {*boost::asio::buffer_sequence_end(seq)} -> std::convertible_to; + } +auto as_asio_buffer_sequence(const T & seq) +{ + return detail::asio_boost_buffer_range(seq); +} + +asio_const_buffer::operator boost::asio::const_buffer() const +{ + return {data(), size()}; +} + +asio_mutable_buffer::operator boost::asio::const_buffer() const +{ + return {data(), size()}; +} + +asio_mutable_buffer::operator boost::asio::mutable_buffer() const +{ + return {data(), size()}; +} + +} + #endif //BOOST_CAPY_ASIO_BOOST_HPP diff --git a/include/boost/capy/asio/buffers.hpp b/include/boost/capy/asio/buffers.hpp new file mode 100644 index 00000000..c3b6fee3 --- /dev/null +++ b/include/boost/capy/asio/buffers.hpp @@ -0,0 +1,437 @@ +// +// Copyright (c) 2025 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_BUFFERS_HPP +#define BOOST_CAPY_ASIO_BUFFERS_HPP + +#include +#include + +namespace asio +{ + +class mutable_buffer; +class const_buffer; + +} + +namespace boost::asio +{ + +class mutable_buffer; +class const_buffer; + +} + +namespace boost::capy +{ + +/** A const buffer type compatible with both capy and Asio. + + Extends capy's `const_buffer` with implicit conversion operators + to both standalone Asio (`::asio::const_buffer`) and Boost.Asio + (`boost::asio::const_buffer`) buffer types. + + This allows seamless use of capy buffers with Asio I/O operations + without explicit conversion. + + @par Example + @code + capy::asio_const_buffer buf(data, size); + + // Works directly with Asio operations + co_await socket.async_write_some(buf, capy::as_io_awaitable); + @endcode + + @see asio_mutable_buffer, as_asio_buffer_sequence +*/ +struct asio_const_buffer : const_buffer +{ + using const_buffer::const_buffer; + using const_buffer::operator=; + + /// Construct from a capy const_buffer. + asio_const_buffer(const_buffer cb) : const_buffer(cb) {} + + /// Convert to standalone Asio const_buffer. + inline + operator ::asio::const_buffer() const; + + /// Convert to Boost.Asio const_buffer. + inline + operator boost::asio::const_buffer() const; +}; + +/** A mutable buffer type compatible with both capy and Asio. + + Extends capy's `mutable_buffer` with implicit conversion operators + to both standalone Asio and Boost.Asio buffer types. Supports + conversion to both mutable and const buffer types. + + This allows seamless use of capy buffers with Asio I/O operations + without explicit conversion. + + @par Example + @code + char data[1024]; + capy::asio_mutable_buffer buf(data, sizeof(data)); + + // Works directly with Asio read operations + auto [ec, n] = co_await socket.async_read_some(buf, capy::as_io_awaitable); + @endcode + + @see asio_const_buffer, as_asio_buffer_sequence +*/ +struct asio_mutable_buffer : mutable_buffer +{ + using mutable_buffer::mutable_buffer; + using mutable_buffer::operator=; + + /// Construct from a capy mutable_buffer. + asio_mutable_buffer(mutable_buffer mb) : mutable_buffer(mb) {} + + /// Convert to standalone Asio mutable_buffer. + inline + operator ::asio::mutable_buffer() const; + + /// Convert to Boost.Asio mutable_buffer. + inline + operator boost::asio::mutable_buffer() const; + + /// Convert to standalone Asio const_buffer. + inline + operator ::asio::const_buffer() const; + + /// Convert to Boost.Asio const_buffer. + inline + operator boost::asio::const_buffer() const; +}; + + +namespace detail +{ + +struct asio_buffer_transformer_t +{ + inline + asio_mutable_buffer operator()(const boost::asio::mutable_buffer &mb) const noexcept; + + inline + asio_const_buffer operator()(const boost::asio::const_buffer &cb) const noexcept; + + inline + asio_mutable_buffer operator()(const ::asio::mutable_buffer &mb) const noexcept; + + inline + asio_const_buffer operator()(const ::asio::const_buffer &cb) const noexcept; + + + asio_mutable_buffer operator()(const mutable_buffer &mb) const noexcept + { + return mb; + } + + asio_const_buffer operator()(const const_buffer &cb) const noexcept + { + return cb; + } + + asio_buffer_transformer_t() noexcept = default; + asio_buffer_transformer_t(const asio_buffer_transformer_t &) noexcept = default; +}; + +constexpr static asio_buffer_transformer_t asio_buffer_transformer; + +/** A bidirectional iterator that transforms buffer types using asio_buffer_transformer. + * + * Wraps an underlying bidirectional iterator and applies asio_buffer_transformer + * to each dereferenced value, converting between Asio buffer types and capy + * asio_const_buffer/asio_mutable_buffer types. + * + * @tparam Iterator The underlying bidirectional iterator type + */ +template +class asio_buffer_iterator +{ +public: + using iterator_type = Iterator; + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = typename std::iterator_traits::difference_type; + using underlying_value = typename std::iterator_traits::value_type; + using value_type = decltype(asio_buffer_transformer(std::declval())); + using reference = value_type; + using pointer = void; + + asio_buffer_iterator() = default; + + explicit asio_buffer_iterator(Iterator it) + noexcept(std::is_nothrow_move_constructible_v) + : it_(std::move(it)) + { + } + + asio_buffer_iterator(const asio_buffer_iterator&) = default; + asio_buffer_iterator(asio_buffer_iterator&&) = default; + asio_buffer_iterator& operator=(const asio_buffer_iterator&) = default; + asio_buffer_iterator& operator=(asio_buffer_iterator&&) = default; + + reference operator*() const + noexcept(noexcept(asio_buffer_transformer(*std::declval()))) + { + return asio_buffer_transformer(*it_); + } + + asio_buffer_iterator& operator++() + noexcept(noexcept(++std::declval())) + { + ++it_; + return *this; + } + + asio_buffer_iterator operator++(int) + noexcept(noexcept(std::declval()++)) + { + asio_buffer_iterator tmp(*this); + ++it_; + return tmp; + } + + asio_buffer_iterator& operator--() + noexcept(noexcept(--std::declval())) + { + --it_; + return *this; + } + + asio_buffer_iterator operator--(int) + noexcept(noexcept(std::declval()--)) + { + asio_buffer_iterator tmp(*this); + --it_; + return tmp; + } + + bool operator==(const asio_buffer_iterator& other) const + noexcept(noexcept(std::declval() == std::declval())) + { + return it_ == other.it_; + } + + bool operator!=(const asio_buffer_iterator& other) const + noexcept(noexcept(std::declval() != std::declval())) + { + return it_ != other.it_; + } + + /// Returns the underlying iterator. + Iterator base() const noexcept(std::is_nothrow_copy_constructible_v) + { + return it_; + } + +private: + Iterator it_{}; +}; + +// Deduction guide +template +asio_buffer_iterator(Iterator) -> asio_buffer_iterator; + + +/** A bidirectional range that transforms buffer sequences using asio_buffer_transformer. + * + * Wraps a buffer sequence and provides begin/end iterators that transform + * buffer elements via asio_buffer_transformer. Satisfies the requirements + * of std::ranges::bidirectional_range. + * + * @tparam Sequence The underlying buffer sequence type + */ +template +class asio_buffer_range +{ +public: + using sequence_type = Sequence; + using iterator = asio_buffer_iterator< + decltype(std::ranges::begin(std::declval()))>; + using const_iterator = iterator; + + asio_buffer_range() = default; + + explicit asio_buffer_range(Sequence seq) + noexcept(std::is_nothrow_move_constructible_v) + : seq_(std::move(seq)) + { + } + + asio_buffer_range(const asio_buffer_range&) = default; + asio_buffer_range(asio_buffer_range&&) = default; + asio_buffer_range& operator=(const asio_buffer_range&) = default; + asio_buffer_range& operator=(asio_buffer_range&&) = default; + + iterator begin() const + noexcept(noexcept(std::ranges::begin(std::declval()))) + { + return iterator(std::ranges::begin(seq_)); + } + + iterator end() const + noexcept(noexcept(std::ranges::end(std::declval()))) + { + return iterator(std::ranges::end(seq_)); + } + + /// Returns true if the range is empty. + bool empty() const + noexcept(noexcept(std::ranges::empty(std::declval()))) + { + return std::ranges::empty(seq_); + } + + /// Returns the underlying sequence. + const Sequence& base() const noexcept { return seq_; } + +private: + Sequence seq_{}; +}; + +// Deduction guide +template +asio_buffer_range(Sequence) -> asio_buffer_range; + +} + + +/** @defgroup as_asio_buffer_sequence as_asio_buffer_sequence + + Bidirectional conversion between Asio and capy buffer sequences. + + The `as_asio_buffer_sequence` function provides seamless conversion + in both directions: + + - **Capy to Asio**: Convert capy buffer sequences for use with Asio + I/O operations. The returned range satisfies Asio's + `MutableBufferSequence` or `ConstBufferSequence` requirements. + + - **Asio to Capy**: Convert Asio buffer sequences for use with capy's + buffer concepts. The returned range satisfies capy's + `MutableBufferSequence` or `ConstBufferSequence` concepts. + + @par Example: Capy to Asio + @code + capy::mutable_buffer_pair buffers = ...; + auto seq = capy::as_asio_buffer_sequence(buffers); + co_await socket.async_read_some(seq, capy::as_io_awaitable); + @endcode + + @par Example: Asio to Capy + @code + std::vector asio_bufs = ...; + auto seq = capy::as_asio_buffer_sequence(asio_bufs); + std::size_t total = capy::buffer_size(seq); // Use capy algorithms + @endcode + + @{ +*/ + +/** Pass through a range already containing asio_const_buffer elements. + + @param rng A bidirectional range of asio_const_buffer + @return The range forwarded unchanged +*/ +template Range> + requires std::same_as, asio_const_buffer> +auto as_asio_buffer_sequence(Range && rng) +{ + return std::forward(rng); +} + +/** Pass through a range already containing asio_mutable_buffer elements. + + @param rng A bidirectional range of asio_mutable_buffer + @return The range forwarded unchanged +*/ +template Range> + requires std::same_as, asio_mutable_buffer> +auto as_asio_buffer_sequence(Range && rng) +{ + return std::forward(rng); +} + +/** Convert a single asio_mutable_buffer to a buffer sequence. + + @param mb The mutable buffer + @return A single-element array containing the buffer +*/ +inline +auto as_asio_buffer_sequence(asio_mutable_buffer mb) +{ + return std::array{mb}; +} + +/** Convert a single asio_const_buffer to a buffer sequence. + + @param cb The const buffer + @return A single-element array containing the buffer +*/ +inline +auto as_asio_buffer_sequence(asio_const_buffer cb) +{ + return std::array{cb}; +} + +/** Convert a single capy mutable_buffer to a buffer sequence. + + @param mb The mutable buffer + @return A single-element array containing an asio_mutable_buffer +*/ +inline +auto as_asio_buffer_sequence(mutable_buffer mb) +{ + return std::array{mb}; +} + +/** Convert a single capy const_buffer to a buffer sequence. + + @param cb The const buffer + @return A single-element array containing an asio_const_buffer +*/ +inline +auto as_asio_buffer_sequence(const_buffer cb) +{ + return std::array{cb}; +} + +/** Convert any buffer sequence for bidirectional Asio/capy compatibility. + + Wraps the buffer sequence in a transforming range that converts + each buffer element to asio_const_buffer or asio_mutable_buffer. + The returned range satisfies both Asio's buffer sequence requirements + and capy's buffer sequence concepts. + + This overload handles: + - Capy buffer sequences (for use with Asio operations) + - Asio buffer sequences (for use with capy concepts/algorithms) + + @param seq The buffer sequence to convert + @return A transforming range over the buffer sequence +*/ +template +inline +auto as_asio_buffer_sequence(const Seq & seq) +{ + return detail::asio_buffer_range(seq); +} + +/** @} */ + +} + + +#endif // BOOST_CAPY_ASIO_BUFFERS_HPP + diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index 7367340f..e63f65d4 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -47,8 +47,11 @@ * @ingroup asio */ -#include +#include #include + +#include +#include #include #include #include @@ -59,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +72,7 @@ #include #include #include +#include namespace boost::capy { @@ -727,7 +732,143 @@ struct std::coroutine_traits< Handler, Executor, Awaitable>; -}; +}; + +namespace boost::capy +{ + + +namespace detail +{ + + +template +struct asio_standalone_buffer_sequence_wrapper +{ + Sequence seq; + auto begin() const {return ::asio::buffer_sequence_begin(seq); } + auto end() const {return ::asio::buffer_sequence_end(seq); } +}; + +/** A bidirectional range that transforms buffer sequences using asio_buffer_transformer. + * + * Wraps an asio buffer sequence and provides begin/end iterators that transform + * buffer elements via asio_buffer_transformer. Uses ::asio::buffer_sequence_begin/end. + * + * @tparam Sequence The underlying buffer sequence type + */ +template +class asio_standalone_buffer_range +{ +public: + using sequence_type = Sequence; + using iterator = asio_buffer_iterator< + decltype(::asio::buffer_sequence_begin(std::declval()))>; + using const_iterator = iterator; + + asio_standalone_buffer_range() = default; + + explicit asio_standalone_buffer_range(Sequence seq) + noexcept(std::is_nothrow_move_constructible_v) + : seq_(std::move(seq)) + { + } + + asio_standalone_buffer_range(const asio_standalone_buffer_range&) = default; + asio_standalone_buffer_range(asio_standalone_buffer_range&&) = default; + asio_standalone_buffer_range& operator=(const asio_standalone_buffer_range&) = default; + asio_standalone_buffer_range& operator=(asio_standalone_buffer_range&&) = default; + + iterator begin() const + noexcept(noexcept(::asio::buffer_sequence_begin(std::declval()))) + { + return iterator(::asio::buffer_sequence_begin(seq_)); + } + + iterator end() const + noexcept(noexcept(::asio::buffer_sequence_end(std::declval()))) + { + return iterator(::asio::buffer_sequence_end(seq_)); + } + + /// Returns the underlying sequence. + const Sequence& base() const noexcept { return seq_; } + +private: + Sequence seq_{}; +}; + +// Deduction guide +template +asio_standalone_buffer_range(Sequence) -> asio_standalone_buffer_range; + + +asio_mutable_buffer asio_buffer_transformer_t:: + operator()(const ::asio::mutable_buffer &mb) const noexcept +{ + return {mb.data(), mb.size()}; +} + +asio_const_buffer asio_buffer_transformer_t:: + operator()(const ::asio::const_buffer &cb) const noexcept +{ + return {cb.data(), cb.size()}; +} + +} + +/** Convert a standalone Asio buffer sequence for bidirectional Asio/capy compatibility. + + Wraps a standalone Asio buffer sequence in a transforming range that converts + each buffer element to asio_const_buffer or asio_mutable_buffer. + The returned range satisfies both standalone Asio's buffer sequence requirements + and capy's buffer sequence concepts. + + This overload uses `::asio::buffer_sequence_begin` and + `::asio::buffer_sequence_end` for iteration. + + @par Example: Asio to Capy + @code + std::vector asio_bufs = ...; + auto seq = capy::as_asio_buffer_sequence(asio_bufs); + std::size_t total = capy::buffer_size(seq); // Use capy algorithms + @endcode + + @param seq The standalone Asio buffer sequence to convert + @return A transforming range over the buffer sequence + + @see as_asio_buffer_sequence in buffers.hpp for capy buffer sequences +*/ +template + requires requires (const T & seq) + { + {::asio::buffer_sequence_begin(seq)} -> std::bidirectional_iterator; + {::asio::buffer_sequence_end(seq)} -> std::bidirectional_iterator; + {*::asio::buffer_sequence_begin(seq)} -> std::convertible_to<::asio::const_buffer>; + {*::asio::buffer_sequence_end(seq)} -> std::convertible_to<::asio::const_buffer>; + } +auto as_asio_buffer_sequence(const T & seq) +{ + return detail::asio_standalone_buffer_range(seq); +} + +asio_const_buffer::operator ::asio::const_buffer() const +{ + return {data(), size()}; +} + +asio_mutable_buffer::operator ::asio::const_buffer() const +{ + return {data(), size()}; +} + +asio_mutable_buffer::operator ::asio::mutable_buffer() const +{ + return {data(), size()}; +} + + +} #endif // BOOST_CAPY_ASIO_STANDALONE_HPP diff --git a/include/boost/capy/buffers/asio.hpp b/include/boost/capy/buffers/asio.hpp deleted file mode 100644 index a2b8be26..00000000 --- a/include/boost/capy/buffers/asio.hpp +++ /dev/null @@ -1,329 +0,0 @@ -// -// Copyright (c) 2025 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_BUFFERS_ASIO_HPP -#define BOOST_CAPY_BUFFERS_ASIO_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace boost { -namespace capy { - -namespace detail { - -// true when BS itself or its range element is convertible -// to asio::const_buffer (i.e. it is an asio-native sequence) -template -constexpr bool is_native_asio_v = false; - -template -constexpr bool is_native_asio_v>> = true; - -template -constexpr bool is_native_asio_v && - std::ranges::bidirectional_range && - std::is_convertible_v< - std::ranges::range_value_t, - asio::const_buffer>>> = true; - -/** Adapts a buffer sequence so its iterators yield the other library's buffer type. - - When the source is an asio-native sequence the adaptor's - iterators produce `capy::mutable_buffer` or `capy::const_buffer`. - Otherwise the source is a capy sequence and the iterators - produce `asio::mutable_buffer` or `asio::const_buffer`. - - @tparam BufferSequence The source buffer sequence type. - @tparam IsMutable Whether the target buffer type is mutable. -*/ -template< - class BufferSequence, - bool IsMutable> -class buffer_sequence_adaptor -{ - static constexpr bool native_asio = - is_native_asio_v; - - static auto get_begin(BufferSequence const& bs) noexcept - { - if constexpr (native_asio) - return asio::buffer_sequence_begin(bs); - else - return capy::begin(bs); - } - - using source_iterator = decltype( - get_begin(std::declval())); - - using source_value_type = std::conditional_t< - native_asio, - std::conditional_t, - std::conditional_t>; - -public: - using value_type = std::conditional_t< - native_asio, - std::conditional_t, - std::conditional_t>; - - class const_iterator - { - source_iterator it_{}; - - public: - using value_type = buffer_sequence_adaptor::value_type; - using reference = value_type; - using pointer = void; - using difference_type = std::ptrdiff_t; - - using iterator_category = std::conditional_t< - std::random_access_iterator, - std::random_access_iterator_tag, - std::bidirectional_iterator_tag>; - - const_iterator() = default; - - explicit const_iterator( - source_iterator it) noexcept - : it_(it) - { - } - - reference operator*() const noexcept - { - source_value_type b(*it_); - return value_type(b.data(), b.size()); - } - - const_iterator& - operator++() noexcept - { - ++it_; - return *this; - } - - const_iterator - operator++(int) noexcept - { - auto tmp = *this; - ++*this; - return tmp; - } - - const_iterator& - operator--() noexcept - { - --it_; - return *this; - } - - const_iterator - operator--(int) noexcept - { - auto tmp = *this; - --*this; - return tmp; - } - - bool operator==( - const_iterator const& other) const noexcept - { - return it_ == other.it_; - } - - bool operator!=( - const_iterator const& other) const noexcept - { - return it_ != other.it_; - } - - //--- random access (conditional) --- - - const_iterator& - operator+=(difference_type n) noexcept - requires std::random_access_iterator - { - it_ += n; - return *this; - } - - const_iterator& - operator-=(difference_type n) noexcept - requires std::random_access_iterator - { - it_ -= n; - return *this; - } - - reference - operator[](difference_type n) const noexcept - requires std::random_access_iterator - { - return *(*this + n); - } - - friend const_iterator - operator+( - const_iterator it, - difference_type n) noexcept - requires std::random_access_iterator - { - it += n; - return it; - } - - friend const_iterator - operator+( - difference_type n, - const_iterator it) noexcept - requires std::random_access_iterator - { - it += n; - return it; - } - - friend const_iterator - operator-( - const_iterator it, - difference_type n) noexcept - requires std::random_access_iterator - { - it -= n; - return it; - } - - friend difference_type - operator-( - const_iterator const& a, - const_iterator const& b) noexcept - requires std::random_access_iterator - { - return a.it_ - b.it_; - } - - auto operator<=>( - const_iterator const& other) const noexcept - requires std::random_access_iterator - { - return it_ <=> other.it_; - } - }; - - template - explicit buffer_sequence_adaptor(BS&& bs) - noexcept(std::is_nothrow_constructible_v< - BufferSequence, BS&&>) - : bs_(std::forward(bs)) - { - } - - const_iterator - begin() const noexcept - { - if constexpr (native_asio) - return const_iterator( - asio::buffer_sequence_begin(bs_)); - else - return const_iterator( - capy::begin(bs_)); - } - - const_iterator - end() const noexcept - { - if constexpr (native_asio) - return const_iterator( - asio::buffer_sequence_end(bs_)); - else - return const_iterator( - capy::end(bs_)); - } - -private: - BufferSequence bs_; -}; - -} // detail - -/** Adapt a capy buffer sequence for use with Asio. - - Returns a wrapper whose iterators dereference to - `asio::mutable_buffer` or `asio::const_buffer`, - depending on whether the source is a - `MutableBufferSequence`. - - @param bs The capy buffer sequence to adapt. - @return An adapted buffer sequence usable with Asio. -*/ -template - requires ConstBufferSequence> -auto -to_asio(BS&& bs) -{ - using Seq = std::remove_cvref_t; - constexpr bool is_mutable = - MutableBufferSequence; - - return detail::buffer_sequence_adaptor< - Seq, is_mutable>( - std::forward(bs)); -} - -/** Adapt an Asio buffer sequence for use with capy. - - Returns a wrapper whose iterators dereference to - `capy::mutable_buffer` or `capy::const_buffer`, - depending on whether the source is convertible to - `asio::mutable_buffer`. Accepts single asio buffers - and ranges of asio buffers. - - @param bs The Asio buffer sequence to adapt. - @return An adapted buffer sequence usable with capy. -*/ -template - requires std::is_convertible_v< - std::remove_cvref_t, asio::const_buffer> || - std::ranges::bidirectional_range> -auto -from_asio(BS&& bs) -{ - using Seq = std::remove_cvref_t; - - constexpr bool is_mutable = [] { - if constexpr (std::is_convertible_v) - return true; - else if constexpr (std::is_convertible_v) - return false; - else - return std::is_convertible_v< - std::ranges::range_value_t, - asio::mutable_buffer>; - }(); - - return detail::buffer_sequence_adaptor< - Seq, is_mutable>( - std::forward(bs)); -} - -} // capy -} // boost - -#endif diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp index 0386ac7f..6d809489 100644 --- a/test/unit/asio.cpp +++ b/test/unit/asio.cpp @@ -8,6 +8,7 @@ // + #include #include #if __has_include() @@ -15,13 +16,17 @@ #include +#include #include #include #include #include #include +#include #include +#include +#include #include #include @@ -165,6 +170,43 @@ struct boost_asio_test while (!done) std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + + + void staticTestBuffer(boost::asio::writable_pipe & wp) + { + boost::asio::mutable_buffer boofer; + boost::capy::MutableBufferSequence auto seq + = boost::capy::as_asio_buffer_sequence(boofer); + + using seq_t = decltype(seq); + + boost::capy::ConstBufferSequence auto cseq + = boost::capy::as_asio_buffer_sequence(boofer); + + using cseq_t = decltype(cseq); + + boost::capy::mutable_buffer_pair p; + auto seq2 = boost::capy::as_asio_buffer_sequence(p); + using seq2_t = decltype(seq2); + + auto s = seq; + auto cs = cseq; + auto s2 = seq2; + + wp.write_some(seq2); + + static_assert(boost::asio::is_mutable_buffer_sequence::value); + static_assert(boost::asio:: is_const_buffer_sequence::value); + + + static_assert(boost::asio::is_mutable_buffer_sequence::value); + static_assert(boost::asio:: is_const_buffer_sequence::value); + + + static_assert(boost::asio::is_mutable_buffer_sequence::value); + static_assert(boost::asio:: is_const_buffer_sequence::value); + } + void run() { diff --git a/test/unit/asio_standalone.cpp b/test/unit/asio_standalone.cpp index 831490b6..ccec9ee5 100644 --- a/test/unit/asio_standalone.cpp +++ b/test/unit/asio_standalone.cpp @@ -9,6 +9,8 @@ #if __has_include() #include +#include +#include #include #include @@ -17,6 +19,7 @@ #include #include #include +#include #include @@ -162,6 +165,41 @@ struct asio_standalone_test while (!done) std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + + void staticTestBuffer(::asio::writable_pipe & wp) + { + ::asio::mutable_buffer boofer; + boost::capy::MutableBufferSequence auto seq + = boost::capy::as_asio_buffer_sequence(boofer); + + using seq_t = decltype(seq); + + boost::capy::ConstBufferSequence auto cseq + = boost::capy::as_asio_buffer_sequence(boofer); + + using cseq_t = decltype(cseq); + + boost::capy::mutable_buffer_pair p; + auto seq2 = boost::capy::as_asio_buffer_sequence(p); + using seq2_t = decltype(seq2); + + auto s = seq; + auto cs = cseq; + auto s2 = seq2; + + wp.write_some(seq2); + + static_assert(::asio::is_mutable_buffer_sequence::value); + static_assert(::asio:: is_const_buffer_sequence::value); + + + static_assert(::asio::is_mutable_buffer_sequence::value); + static_assert(::asio:: is_const_buffer_sequence::value); + + + static_assert(::asio::is_mutable_buffer_sequence::value); + static_assert(::asio:: is_const_buffer_sequence::value); + } void run() { diff --git a/test/unit/buffers/asio.cpp b/test/unit/buffers/asio.cpp deleted file mode 100644 index 2be26180..00000000 --- a/test/unit/buffers/asio.cpp +++ /dev/null @@ -1,329 +0,0 @@ -// -// Copyright (c) 2025 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 BOOST_CAPY_HAS_ASIO - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "test_buffers.hpp" - -namespace boost { -namespace capy { - -//------------------------------------------------ -// to_asio result satisfies asio buffer sequence traits - -using to_asio_const_t = decltype( - to_asio(std::declval const&>())); -using to_asio_mutable_t = decltype( - to_asio(std::declval const&>())); - -static_assert(asio::is_const_buffer_sequence::value); -static_assert(asio::is_const_buffer_sequence::value); -static_assert(!asio::is_mutable_buffer_sequence::value); -static_assert(asio::is_mutable_buffer_sequence::value); - -//------------------------------------------------ -// from_asio result satisfies capy buffer sequence concepts - -using from_asio_const_t = decltype( - from_asio(std::declval>())); -using from_asio_mutable_t = decltype( - from_asio(std::declval>())); - -static_assert(ConstBufferSequence); -static_assert(ConstBufferSequence); -static_assert(!MutableBufferSequence); -static_assert(MutableBufferSequence); - -//------------------------------------------------ -// to_asio iterator dereferences to asio buffer types - -static_assert(std::is_same_v< - to_asio_const_t::const_iterator::value_type, - asio::const_buffer>); -static_assert(std::is_same_v< - to_asio_mutable_t::const_iterator::value_type, - asio::mutable_buffer>); - -//------------------------------------------------ -// from_asio iterator dereferences to capy buffer types - -static_assert(std::is_same_v< - from_asio_const_t::const_iterator::value_type, - capy::const_buffer>); -static_assert(std::is_same_v< - from_asio_mutable_t::const_iterator::value_type, - capy::mutable_buffer>); - -//------------------------------------------------ - -struct asio_test -{ - void - test_to_asio_const() - { - char const data[] = "Hello"; - const_buffer cb(data, 5); - - auto adapted = to_asio(cb); - auto it = adapted.begin(); - auto end = adapted.end(); - BOOST_TEST(it != end); - - asio::const_buffer ab = *it; - BOOST_TEST_EQ(ab.data(), cb.data()); - BOOST_TEST_EQ(ab.size(), cb.size()); - - ++it; - BOOST_TEST(it == end); - } - - void - test_to_asio_mutable() - { - char data[] = "Hello"; - mutable_buffer mb(data, 5); - - auto adapted = to_asio(mb); - auto it = adapted.begin(); - auto end = adapted.end(); - BOOST_TEST(it != end); - - asio::mutable_buffer ab = *it; - BOOST_TEST_EQ(ab.data(), mb.data()); - BOOST_TEST_EQ(ab.size(), mb.size()); - - ++it; - BOOST_TEST(it == end); - } - - void - test_to_asio_array() - { - char d1[] = "abc"; - char d2[] = "defgh"; - mutable_buffer_array<4> bufs; - bufs = mutable_buffer_array<4>( - std::array{{ - mutable_buffer(d1, 3), - mutable_buffer(d2, 5) - }}); - - auto adapted = to_asio(bufs); - auto it = adapted.begin(); - auto end = adapted.end(); - - asio::mutable_buffer ab0 = *it; - BOOST_TEST_EQ(ab0.data(), static_cast(d1)); - BOOST_TEST_EQ(ab0.size(), 3u); - ++it; - - asio::mutable_buffer ab1 = *it; - BOOST_TEST_EQ(ab1.data(), static_cast(d2)); - BOOST_TEST_EQ(ab1.size(), 5u); - ++it; - - BOOST_TEST(it == end); - } - - void - test_from_asio_const() - { - char const data[] = "Hello"; - asio::const_buffer ab(data, 5); - std::span sp(&ab, 1); - - auto adapted = from_asio(sp); - auto it = adapted.begin(); - auto end = adapted.end(); - BOOST_TEST(it != end); - - const_buffer cb = *it; - BOOST_TEST_EQ(cb.data(), ab.data()); - BOOST_TEST_EQ(cb.size(), ab.size()); - - ++it; - BOOST_TEST(it == end); - } - - void - test_from_asio_mutable() - { - char data[] = "Hello"; - asio::mutable_buffer ab(data, 5); - std::span sp(&ab, 1); - - auto adapted = from_asio(sp); - auto it = adapted.begin(); - auto end = adapted.end(); - BOOST_TEST(it != end); - - mutable_buffer mb = *it; - BOOST_TEST_EQ(mb.data(), ab.data()); - BOOST_TEST_EQ(mb.size(), ab.size()); - - ++it; - BOOST_TEST(it == end); - } - - void - test_from_asio_array() - { - char d1[] = "abc"; - char d2[] = "defgh"; - std::array asio_bufs{{ - asio::mutable_buffer(d1, 3), - asio::mutable_buffer(d2, 5) - }}; - - auto adapted = from_asio(asio_bufs); - auto it = adapted.begin(); - auto end = adapted.end(); - - mutable_buffer mb0 = *it; - BOOST_TEST_EQ(mb0.data(), static_cast(d1)); - BOOST_TEST_EQ(mb0.size(), 3u); - ++it; - - mutable_buffer mb1 = *it; - BOOST_TEST_EQ(mb1.data(), static_cast(d2)); - BOOST_TEST_EQ(mb1.size(), 5u); - ++it; - - BOOST_TEST(it == end); - } - - void - test_roundtrip() - { - // capy -> asio -> capy preserves data/size - char data[] = "roundtrip"; - mutable_buffer mb(data, 9); - - auto asio_adapted = to_asio(mb); - auto it = asio_adapted.begin(); - asio::mutable_buffer ab = *it; - - std::span sp(&ab, 1); - auto capy_adapted = from_asio(sp); - auto it2 = capy_adapted.begin(); - mutable_buffer mb2 = *it2; - - BOOST_TEST_EQ(mb2.data(), mb.data()); - BOOST_TEST_EQ(mb2.size(), mb.size()); - } - - void - test_bidirectional() - { - char d1[] = "ab"; - char d2[] = "cd"; - std::array asio_bufs{{ - asio::const_buffer(d1, 2), - asio::const_buffer(d2, 2) - }}; - - auto adapted = from_asio(asio_bufs); - auto it = adapted.end(); - auto beg = adapted.begin(); - - --it; - const_buffer cb1 = *it; - BOOST_TEST_EQ(cb1.data(), static_cast(d2)); - - --it; - const_buffer cb0 = *it; - BOOST_TEST_EQ(cb0.data(), static_cast(d1)); - - BOOST_TEST(it == beg); - } - - void - test_random_access() - { - char d1[] = "ab"; - char d2[] = "cd"; - char d3[] = "ef"; - std::array asio_bufs{{ - asio::const_buffer(d1, 2), - asio::const_buffer(d2, 2), - asio::const_buffer(d3, 2) - }}; - - auto adapted = from_asio(asio_bufs); - auto beg = adapted.begin(); - auto end = adapted.end(); - - BOOST_TEST_EQ(end - beg, 3); - BOOST_TEST(beg < end); - - auto mid = beg + 1; - const_buffer cb = *mid; - BOOST_TEST_EQ(cb.data(), static_cast(d2)); - - cb = beg[2]; - BOOST_TEST_EQ(cb.data(), static_cast(d3)); - - mid += 1; - cb = *mid; - BOOST_TEST_EQ(cb.data(), static_cast(d3)); - - mid -= 2; - cb = *mid; - BOOST_TEST_EQ(cb.data(), static_cast(d1)); - } - - void - test_move_semantics() - { - char d1[] = "abc"; - mutable_buffer_array<4> bufs; - bufs = mutable_buffer_array<4>( - mutable_buffer(d1, 3)); - - auto adapted = to_asio(std::move(bufs)); - auto it = adapted.begin(); - asio::mutable_buffer ab = *it; - BOOST_TEST_EQ(ab.data(), static_cast(d1)); - BOOST_TEST_EQ(ab.size(), 3u); - } - - void - run() - { - test_to_asio_const(); - test_to_asio_mutable(); - test_to_asio_array(); - test_from_asio_const(); - test_from_asio_mutable(); - test_from_asio_array(); - test_roundtrip(); - test_bidirectional(); - test_random_access(); - test_move_semantics(); - } -}; - -TEST_SUITE( - asio_test, - "boost.capy.buffers.asio"); - -} // capy -} // boost - -#endif // BOOST_CAPY_HAS_ASIO From 2938339281f45595e80f3e0fe72c7071f74d65a9 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 20 Apr 2026 21:25:23 +0800 Subject: [PATCH 28/29] Stream support --- .../4.coroutines/4i.asio-integration.adoc | 141 ++++- include/boost/capy/asio/boost.hpp | 56 +- include/boost/capy/asio/buffers.hpp | 18 +- .../detail/asio_coroutine_unique_handle.hpp | 9 +- .../capy/asio/detail/completion_handler.hpp | 27 +- .../capy/asio/detail/completion_traits.hpp | 3 +- include/boost/capy/asio/spawn.hpp | 2 +- include/boost/capy/asio/standalone.hpp | 45 +- include/boost/capy/asio/stream.hpp | 511 ++++++++++++++++++ include/boost/capy/test/read_stream.hpp | 2 +- include/boost/capy/test/stream.hpp | 2 +- include/boost/capy/test/write_stream.hpp | 2 +- test/unit/asio.cpp | 91 +++- test/unit/asio_standalone.cpp | 90 +++ 14 files changed, 938 insertions(+), 61 deletions(-) create mode 100644 include/boost/capy/asio/stream.hpp diff --git a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc index 224c422a..4ad097cb 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4i.asio-integration.adoc @@ -200,33 +200,32 @@ capy::asio_spawn(exec, compute())( }); // With use_awaitable (in an Asio coroutine) -auto [ep, result] = co_await capy::asio_spawn(exec, compute())( - asio::use_awaitable); +auto [ep, result] = co_await capy::asio_spawn(exec, compute())); // With use_future -auto future = capy::asio_spawn(exec, compute())(asio::use_future); +auto future = capy::asio_spawn(exec, compute(), asio::use_future); auto [ep, result] = future.get(); ---- === Completion Signature The completion signature depends on the coroutine's return type -and the exception specification of `await_resume`: +and the `noexcept` specification of `await_resume`: [cols="2,2"] |=== | Coroutine | Completion Signature -| `io_task` (may throw) +| `IoAwaitable` (may throw) | `void(std::exception_ptr, T)` -| `io_task` (noexcept) +| `IoAwaitable` (noexcept) | `void(T)` -| `io_task` (may throw) +| `IoAwaitable` (may throw) | `void(std::exception_ptr)` -| `io_task` (noexcept) +| `IoAwaitable` (noexcept) | `void()` |=== @@ -312,7 +311,7 @@ using tcp = asio::ip::tcp; // Rebind socket to use as_io_awaitable by default using socket_type = capy::as_io_awaitable_t::as_default_on_t; -capy::io_task handle_client(socket_type socket) +capy::task handle_client(socket_type socket) { std::array buffer; @@ -342,7 +341,7 @@ capy::io_task handle_client(socket_type socket) } } -capy::io_task accept_loop(tcp::acceptor& acceptor) +capy::task accept_loop(tcp::acceptor& acceptor) { auto exec = co_await capy::this_coro::executor; @@ -492,6 +491,107 @@ capy::io_task read_message( } ---- +== Stream Adapters + +Capy provides bidirectional stream adapters for converting between Asio's `AsyncReadStream`/`AsyncWriteStream` types and capy's `ReadStream`/`WriteStream` concepts. + +=== Capy Streams to Asio + +The `async_read_stream`, `async_write_stream`, and `async_stream` classes wrap capy streams to provide Asio-style `async_read_some` and `async_write_some` operations with completion token support. + +[source,cpp] +---- +#include + +// Wrap a capy stream for Asio +capy::any_stream my_stream = ...; +auto exec = capy::wrap_asio_executor(io.get_executor()); + +capy::async_stream asio_compatible(std::move(my_stream), exec); + +// Use with Asio-style async operations +auto [ec, n] = co_await asio_compatible.async_read_some( + buffer, capy::as_io_awaitable); +---- + +These adapters are useful when you have a capy stream but need to use it with code expecting Asio's async model. + +[cols="1,2"] +|=== +| Class | Description + +| `async_read_stream` +| Wraps a capy `ReadStream`, provides `async_read_some` + +| `async_write_stream` +| Wraps a capy `WriteStream`, provides `async_write_some` + +| `async_stream` +| Wraps a capy `Stream`, provides both operations +|=== + +=== Asio Streams to Capy + +The `asio_read_stream`, `asio_write_stream`, and `asio_stream` classes wrap Asio streams to satisfy capy's stream concepts, using `as_io_awaitable` internally. + +[source,cpp] +---- +#include + +// Wrap an Asio socket for capy +boost::asio::ip::tcp::socket socket(io); +capy::asio_stream stream(std::move(socket)); + +// Use with capy's stream interface +auto result = co_await stream.read_some(buffer); +auto [ec, bytes] = result; +---- + +These adapters allow Asio I/O objects to be used with capy's stream algorithms and abstractions. + +[cols="1,2"] +|=== +| Class | Description + +| `asio_read_stream` +| Wraps an Asio `AsyncReadStream`, provides `read_some` + +| `asio_write_stream` +| Wraps an Asio `AsyncWriteStream`, provides `write_some` + +| `asio_stream` +| Wraps an Asio stream, provides both operations +|=== + +=== Example: Bridging Stream Types + +[source,cpp] +---- +#include +#include + +// Convert Asio socket to capy stream +capy::task process_connection(boost::asio::ip::tcp::socket socket) +{ + // Wrap the Asio socket as a capy stream + capy::asio_stream stream(std::move(socket)); + + // Now use capy stream algorithms + std::array buf; + capy::mutable_buffer buffer(buf.data(), buf.size()); + + while (true) + { + auto [ec, n] = co_await stream.read_some(buffer); + if (ec) + break; + + // Echo back + co_await stream.write_some(capy::const_buffer(buf.data(), n)); + } +} +---- + == Reference [cols="1,2"] @@ -518,6 +618,9 @@ capy::io_task read_message( | `` | The `wrap_asio_executor` function (included by above) + +| `` +| Stream adapters for bidirectional Asio/capy conversion |=== [cols="1,2"] @@ -547,5 +650,23 @@ capy::io_task read_message( | `as_asio_buffer_sequence` | Convert any buffer sequence for Asio compatibility + +| `async_read_stream` +| Wrap capy ReadStream for Asio async operations + +| `async_write_stream` +| Wrap capy WriteStream for Asio async operations + +| `async_stream` +| Wrap capy Stream for Asio async operations + +| `asio_read_stream` +| Wrap Asio AsyncReadStream for capy ReadStream concept + +| `asio_write_stream` +| Wrap Asio AsyncWriteStream for capy WriteStream concept + +| `asio_stream` +| Wrap Asio stream for capy Stream concept |=== diff --git a/include/boost/capy/asio/boost.hpp b/include/boost/capy/asio/boost.hpp index 6237743f..1439bf19 100644 --- a/include/boost/capy/asio/boost.hpp +++ b/include/boost/capy/asio/boost.hpp @@ -47,6 +47,7 @@ */ #include +#include #include #include @@ -70,6 +71,7 @@ #include #include #include +#include #include namespace boost::capy @@ -546,6 +548,7 @@ struct boost_asio_promise_type_allocator_base void* p = static_cast(mem) + n; new (p) allocator_type(std::move(allocator)); + return mem; } void operator delete(void * ptr, std::size_t n) @@ -564,6 +567,10 @@ struct boost_asio_promise_type_allocator_base } }; +template<> +struct boost_asio_promise_type_allocator_base> +{ +}; template struct boost_asio_init_promise_type @@ -577,7 +584,9 @@ struct boost_asio_init_promise_type Handler & h, Ex & exec, Awaitable &) - : handler(h), ex(exec) {} + : handler(h), ex(exec) + { + } Handler & handler; Ex &ex; @@ -594,27 +603,27 @@ struct boost_asio_init_promise_type Handler handler; Ex ex; args_type args; - + + bool await_ready() const {return false;} - void await_suspend( - std::coroutine_handle h) - { - auto h_ = std::move(handler); - auto args_ = std::move(args); - asio_executor_adapter ex_ = std::move(ex); - h.destroy(); + auto await_suspend( + std::coroutine_handle h) + { auto handler_ = std::apply( [&](auto ... args) { - return boost::asio::append(std::move(h_), std::move(args)...); + return boost::asio::append(std::move(handler), + std::move(args)...); }, - args_); + detail::decomposed_types(std::move(args))); - auto exec = - boost::asio::get_associated_immediate_executor(handler_, ex_); - boost::asio::dispatch(exec, std::move(handler_)); + auto exec = boost::asio::get_associated_immediate_executor( + handler_, + asio_executor_adapter(std::move(ex))); + h.destroy(); + boost::asio::dispatch(exec, std::move(handler_)); } void await_resume() const {} }; @@ -641,14 +650,10 @@ struct boost_asio_init_promise_type bool await_ready() {return r.await_ready(); } - std::coroutine_handle<> await_suspend( + auto await_suspend( std::coroutine_handle tr) { - // always post in - auto h = r.handle(); - auto & p = h.promise(); env.executor = ex; - env.stop_token = stop_src.get_token(); cancel_slot = boost::asio::get_associated_cancellation_slot(tr.promise().handler); @@ -663,9 +668,16 @@ struct boost_asio_init_promise_type }); env.frame_allocator = get_current_frame_allocator(); - - c.h = r.await_suspend(tr, &env); - return ex.dispatch(c); + using suspend_kind = decltype(r.await_suspend(tr, &env)); + if constexpr (std::is_void_v) + r.await_suspend(tr, &env); + else if constexpr (std::same_as) + return r.await_suspend(tr, &env); + else + { + c.h = r.await_suspend(tr, &env); + return ex.dispatch(c); + } } completion_tuple_for_io_awaitable await_resume() diff --git a/include/boost/capy/asio/buffers.hpp b/include/boost/capy/asio/buffers.hpp index c3b6fee3..d7437557 100644 --- a/include/boost/capy/asio/buffers.hpp +++ b/include/boost/capy/asio/buffers.hpp @@ -11,6 +11,7 @@ #define BOOST_CAPY_ASIO_BUFFERS_HPP #include +#include #include namespace asio @@ -396,10 +397,24 @@ auto as_asio_buffer_sequence(mutable_buffer mb) return std::array{mb}; } -/** Convert a single capy const_buffer to a buffer sequence. +/** Convert a buffer type that might support both mutable & const buffer + to an asio buffer sequence. + + @param buf The const buffer + @return A single-element array containing an asio_mutable_buffer + +*/ +template Buffer> +inline +auto as_asio_buffer_sequence(const Buffer & buf) +{ + return as_asio_buffer_sequence(static_cast(buf)); +} +/** Convert a single capy const_buffer to a buffer sequence. @param cb The const buffer @return A single-element array containing an asio_const_buffer + */ inline auto as_asio_buffer_sequence(const_buffer cb) @@ -422,6 +437,7 @@ auto as_asio_buffer_sequence(const_buffer cb) @return A transforming range over the buffer sequence */ template + requires (!std::convertible_to && !std::convertible_to) inline auto as_asio_buffer_sequence(const Seq & seq) { diff --git a/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp index f198b00d..e7189d5b 100644 --- a/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp +++ b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp @@ -38,9 +38,14 @@ struct asio_coroutine_unique_handle void operator()() { - std::coroutine_handle::from_address( + release().resume(); + } + + std::coroutine_handle<> release() + { + return std::coroutine_handle::from_address( handle.release() - ).resume(); + ); } }; diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp index 21b42bfa..bd5284bc 100644 --- a/include/boost/capy/asio/detail/completion_handler.hpp +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -56,7 +57,8 @@ struct asio_immediate_executor_helper exec.post( make_continuation( std::forward(fn), - exec.context().get_frame_allocator())); + std::pmr::polymorphic_allocator( + exec.context().get_frame_allocator()))); } } @@ -130,11 +132,15 @@ struct asio_coroutine_completion_handler { result.emplace(std::forward(args)...); + auto h = handle.release(); + if (completed_immediately != nullptr && *completed_immediately == completed_immediately_t::maybe) + { *completed_immediately = completed_immediately_t::yes; + } else - std::move(handle)(); + h(); } }; @@ -152,7 +158,22 @@ struct async_result_impl_result_tuple using type = io_result; }; +inline std::tuple<> make_async_result(const std::tuple<> &) { return {}; } +template +inline auto make_async_result(std::tuple && tup) +{ + if constexpr (std::convertible_to) + return std::apply( + [](auto &&e, auto &&... args) + { + return io_result(std::move(e), std::move(args)...); + }, + std::move(tup)); + + else + return std::move(tup); +} template @@ -203,7 +224,7 @@ struct async_result_impl auto await_resume() { - return std::move(*result_); + return make_async_result(std::move(*result_)); } diff --git a/include/boost/capy/asio/detail/completion_traits.hpp b/include/boost/capy/asio/detail/completion_traits.hpp index dae4bf3f..8b8b24e5 100644 --- a/include/boost/capy/asio/detail/completion_traits.hpp +++ b/include/boost/capy/asio/detail/completion_traits.hpp @@ -26,6 +26,7 @@ struct completion_traits { using signature_type = void(std::exception_ptr, ResultType); using result_type = std::tuple; + }; template @@ -39,7 +40,7 @@ struct completion_traits template struct completion_traits, true> { - using signature_type = void(Ts...); + using signature_type = void(std::error_code, Ts...); using result_type = io_result; }; diff --git a/include/boost/capy/asio/spawn.hpp b/include/boost/capy/asio/spawn.hpp index 6dbed606..550d2f57 100644 --- a/include/boost/capy/asio/spawn.hpp +++ b/include/boost/capy/asio/spawn.hpp @@ -167,7 +167,7 @@ struct asio_spawn_op * * @par Example * @code - * capy::io_task compute() { co_return 42; } + * capy::task compute() { co_return 42; } * * // Using with Boost.Asio * asio_spawn(executor, compute())( diff --git a/include/boost/capy/asio/standalone.hpp b/include/boost/capy/asio/standalone.hpp index e63f65d4..06b50ed6 100644 --- a/include/boost/capy/asio/standalone.hpp +++ b/include/boost/capy/asio/standalone.hpp @@ -49,6 +49,8 @@ #include #include +#include + #include #include @@ -56,8 +58,7 @@ #include #include #include - - +#include #include #include #include @@ -525,6 +526,11 @@ struct boost_asio_standalone_promise_type_allocator_base } }; +template<> +struct boost_asio_standalone_promise_type_allocator_base> +{ +}; + template struct boost_asio_standalone_init_promise_type @@ -561,21 +567,21 @@ struct boost_asio_standalone_init_promise_type void await_suspend( std::coroutine_handle h) { - auto h_ = std::move(handler); - auto args_ = std::move(args); - asio_executor_adapter ex_ = std::move(ex); - h.destroy(); - auto handler_ = std::apply( [&](auto ... args) { - return ::asio::append(std::move(h_), std::move(args)...); + return ::asio::append(std::move(handler), + std::move(args)...); }, - args_); + detail::decomposed_types(std::move(args))); - auto exec = ::asio::get_associated_immediate_executor(handler_, ex_); - ::asio::dispatch(exec, std::move(handler_)); + auto exec = ::asio::get_associated_immediate_executor( + handler_, + asio_executor_adapter(std::move(ex))); + + h.destroy(); + ::asio::dispatch(exec, std::move(handler_)); } void await_resume() const {} }; @@ -602,12 +608,9 @@ struct boost_asio_standalone_init_promise_type bool await_ready() {return r.await_ready(); } - std::coroutine_handle<> await_suspend( + auto await_suspend( std::coroutine_handle tr) { - // always post in - auto h = r.handle(); - auto & p = h.promise(); env.executor = ex; env.stop_token = stop_src.get_token(); @@ -624,8 +627,16 @@ struct boost_asio_standalone_init_promise_type env.frame_allocator = get_current_frame_allocator(); - c.h = r.await_suspend(tr, &env); - return ex.dispatch(c); + using suspend_kind = decltype(r.await_suspend(tr, &env)); + if constexpr (std::is_void_v) + r.await_suspend(tr, &env); + else if constexpr (std::same_as) + return r.await_suspend(tr, &env); + else + { + c.h = r.await_suspend(tr, &env); + return ex.dispatch(c); + } } completion_tuple_for_io_awaitable await_resume() diff --git a/include/boost/capy/asio/stream.hpp b/include/boost/capy/asio/stream.hpp new file mode 100644 index 00000000..45445cc9 --- /dev/null +++ b/include/boost/capy/asio/stream.hpp @@ -0,0 +1,511 @@ +// +// 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_STREAM_HPP +#define BOOST_CAPY_ASIO_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::capy +{ + +/** Wraps a capy ReadStream for use with Asio async operations. + + Adapts a capy `ReadStream` to provide Asio-style `async_read_some` + operations with completion token support. + + @tparam Stream The underlying capy ReadStream type + @tparam Exec The executor type + + @par Example + @code + capy::any_read_stream stream = ...; + auto exec = capy::wrap_asio_executor(io.get_executor()); + capy::async_read_stream reader(std::move(stream), exec); + + auto [ec, n] = co_await reader.async_read_some(buffer, capy::as_io_awaitable); + @endcode +*/ +template +struct async_read_stream +{ + using executor_type = asio_executor_adapter; + using next_layer_type = Stream; + + /// Default constructor. + async_read_stream() = default; + + /// Construct from a stream and executor. + async_read_stream(Stream stream, Exec executor) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) + : stream_(std::move(stream)) + , executor_(std::move(executor)) + { + } + + async_read_stream(const async_read_stream&) + noexcept(std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_constructible_v) = default; + async_read_stream(async_read_stream&&) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) = default; + async_read_stream& operator=(const async_read_stream&) + noexcept(std::is_nothrow_copy_assignable_v && + std::is_nothrow_copy_assignable_v) = default; + async_read_stream& operator=(async_read_stream&&) + noexcept(std::is_nothrow_move_assignable_v && + std::is_nothrow_move_assignable_v) = default; + + /// Returns the executor adapter for Asio compatibility. + executor_type get_executor() const { return executor_; } + + /// Initiates an async read, returning a spawn handle. + template + auto async_read_some(Buffer && buffer) + { + return asio_spawn( + executor_, + stream_.read_some(as_asio_buffer_sequence(std::forward(buffer)))); + } + + /// Initiates an async read with a completion token. + template + auto async_read_some(Buffer && buffer, CompletionToken && token) + { + return asio_spawn( + executor_, + stream_.read_some(as_asio_buffer_sequence(std::forward(buffer))), + std::forward(token)); + } + + /// Returns a reference inter to the underlying stream. + next_layer_type& next_layer() { return stream_; } + /// Returns a const reference to the underlying stream. + const next_layer_type& next_layer() const { return stream_; } + + private: + Stream stream_; + Exec executor_; +}; + +// Deduction guides +template +async_read_stream(Stream, Exec) -> async_read_stream; + + +/** Wraps a capy WriteStream for use with Asio async operations. + + Adapts a capy `WriteStream` to provide Asio-style `async_write_some` + operations with completion token support. + + @tparam Stream The underlying capy WriteStream type + @tparam Exec The executor type + + @par Example + @code + capy::any_write_stream stream = ...; + auto exec = capy::wrap_asio_executor(io.get_executor()); + capy::async_write_stream writer(std::move(stream), exec); + + auto [ec, n] = co_await writer.async_write_some(buffer, capy::as_io_awaitable); + @endcode +*/ +template +struct async_write_stream +{ + using executor_type = asio_executor_adapter; + using next_layer_type = Stream; + + /// Default constructor. + async_write_stream() = default; + + /// Construct from a stream and executor. + async_write_stream(Stream stream, Exec executor) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) + : stream_(std::move(stream)) + , executor_(std::move(executor)) + { + } + + async_write_stream(const async_write_stream&) + noexcept(std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_constructible_v) = default; + async_write_stream(async_write_stream&&) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) = default; + async_write_stream& operator=(const async_write_stream&) + noexcept(std::is_nothrow_copy_assignable_v && + std::is_nothrow_copy_assignable_v) = default; + async_write_stream& operator=(async_write_stream&&) + noexcept(std::is_nothrow_move_assignable_v && + std::is_nothrow_move_assignable_v) = default; + + /// Returns the executor adapter for Asio compatibility. + executor_type get_executor() const { return executor_; } + + /// Initiates an async write, returning a spawn handle. + template + auto async_write_some(Buffer && buffer) + { + + return asio_spawn( + executor_, + stream_.write_some(as_asio_buffer_sequence(std::forward(buffer)))); + } + + /// Initiates an async write with a completion token. + template + auto async_write_some(Buffer && buffer, CompletionToken && token) + { + return asio_spawn( + executor_, + stream_.write_some(as_asio_buffer_sequence(std::forward(buffer))), + std::forward(token)); + } + + /// Returns a reference to the underlying stream. + next_layer_type& next_layer() { return stream_; } + /// Returns a const reference to the underlying stream. + const next_layer_type& next_layer() const { return stream_; } + + private: + Stream stream_; + Exec executor_; +}; + +// Deduction guides +template +async_write_stream(Stream, Exec) -> async_write_stream; + + +/** Wraps a capy Stream for use with Asio async operations. + + Adapts a capy `Stream` (supporting both read and write) to provide + Asio-style `async_read_some` and `async_write_some` operations + with completion token support. + + @tparam Stream The underlying capy Stream type + @tparam Exec The executor type + + @par Example + @code + capy::any_stream stream = ...; + auto exec = capy::wrap_asio_executor(io.get_executor()); + capy::async_stream sock(std::move(stream), exec); + + // Read + auto [ec1, n1] = co_await sock.async_read_some(read_buf, capy::as_io_awaitable); + + // Write + auto [ec2, n2] = co_await sock.async_write_some(write_buf, capy::as_io_awaitable); + @endcode +*/ +template +struct async_stream +{ + using executor_type = asio_executor_adapter; + using next_layer_type = Stream; + + /// Default constructor. + async_stream() = default; + + /// Construct from a stream and executor. + async_stream(Stream stream, Exec executor) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) + : stream_(std::move(stream)) + , executor_(std::move(executor)) + { + } + + async_stream(const async_stream&) + noexcept(std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_constructible_v) = default; + async_stream(async_stream&&) + noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) = default; + async_stream& operator=(const async_stream&) + noexcept(std::is_nothrow_copy_assignable_v && + std::is_nothrow_copy_assignable_v) = default; + async_stream& operator=(async_stream&&) + noexcept(std::is_nothrow_move_assignable_v && + std::is_nothrow_move_assignable_v) = default; + + /// Returns the executor adapter for Asio compatibility. + executor_type get_executor() const { return executor_; } + + /// Initiates an async read, returning a spawn handle. + template + auto async_read_some(Buffer && buffer) + { + return asio_spawn( + executor_, + stream_.read_some(as_asio_buffer_sequence(std::forward(buffer)))); + } + + /// Initiates an async read with a completion token. + template + auto async_read_some(Buffer && buffer, CompletionToken && token) + { + return asio_spawn( + executor_, + stream_.read_some(as_asio_buffer_sequence(std::forward(buffer))), + std::forward(token)); + } + + /// Initiates an async write, returning a spawn handle. + template + auto async_write_some(Buffer && buffer) + { + return asio_spawn( + executor_, + stream_.write_some(as_asio_buffer_sequence(std::forward(buffer)))); + } + + /// Initiates an async write with a completion token. + template + auto async_write_some(Buffer && buffer, CompletionToken && token) + { + return asio_spawn( + executor_, + stream_.write_some(as_asio_buffer_sequence(std::forward(buffer))), + std::forward(token)); + } + + /// Returns a reference to the underlying stream. + next_layer_type& next_layer() { return stream_; } + /// Returns a const reference to the underlying stream. + const next_layer_type& next_layer() const { return stream_; } + + private: + Stream stream_; + Exec executor_; +}; + +// Deduction guides +template +async_stream(StreamT, Exec) -> async_stream; + +/** Wraps an Asio AsyncReadStream for use with capy's ReadStream concept. + + Adapts an Asio `AsyncReadStream` to provide capy-style `read_some` + operations that return an IoAwaitable. + + @tparam AsyncReadStream The underlying Asio AsyncReadStream type + + @par Example + @code + boost::asio::ip::tcp::socket socket(io); + capy::asio_read_stream reader(std::move(socket)); + + auto result = co_await reader.read_some(buffer); + @endcode +*/ +template +struct asio_read_stream +{ + using executor_type = typename AsyncReadStream::executor_type; + using next_layer_type = AsyncReadStream; + + /// Default constructor. + asio_read_stream() = default; + + /// Construct from an Asio stream. + explicit asio_read_stream(AsyncReadStream stream) + noexcept(std::is_nothrow_move_constructible_v) + : stream_(std::move(stream)) + { + } + + asio_read_stream(const asio_read_stream&) + noexcept(std::is_nothrow_copy_constructible_v) = default; + asio_read_stream(asio_read_stream&&) + noexcept(std::is_nothrow_move_constructible_v) = default; + asio_read_stream& operator=(const asio_read_stream&) + noexcept(std::is_nothrow_copy_assignable_v) = default; + asio_read_stream& operator=(asio_read_stream&&) + noexcept(std::is_nothrow_move_assignable_v) = default; + + /// Returns the executor from the underlying stream. + executor_type get_executor() const { return stream_.get_executor(); } + + /// Initiates an async read, returning an IoAwaitable. + template + auto read_some(Seq buffers) + { + return stream_.async_read_some(as_asio_buffer_sequence(buffers), as_io_awaitable); + } + + /// Returns a reference to the underlying stream. + next_layer_type& next_layer() { return stream_; } + /// Returns a const reference to the underlying stream. + const next_layer_type& next_layer() const { return stream_; } + + private: + AsyncReadStream stream_; +}; + +// Deduction guide +template +asio_read_stream(AsyncReadStream) -> asio_read_stream; + + +/** Wraps an Asio AsyncWriteStream for use with capy's WriteStream concept. + + Adapts an Asio `AsyncWriteStream` to provide capy-style `write_some` + operations that return an IoAwaitable. + + @tparam AsyncWriteStream The underlying Asio AsyncWriteStream type + + @par Example + @code + boost::asio::ip::tcp::socket socket(io); + capy::asio_write_stream writer(std::move(socket)); + + auto result = co_await writer.write_some(buffer); + @endcode +*/ +template +struct asio_write_stream +{ + using executor_type = typename AsyncWriteStream::executor_type; + using next_layer_type = AsyncWriteStream; + + /// Default constructor. + asio_write_stream() = default; + + /// Construct from an Asio stream. + explicit asio_write_stream(AsyncWriteStream stream) + noexcept(std::is_nothrow_move_constructible_v) + : stream_(std::move(stream)) + { + } + + asio_write_stream(const asio_write_stream&) + noexcept(std::is_nothrow_copy_constructible_v) = default; + asio_write_stream(asio_write_stream&&) + noexcept(std::is_nothrow_move_constructible_v) = default; + asio_write_stream& operator=(const asio_write_stream&) + noexcept(std::is_nothrow_copy_assignable_v) = default; + asio_write_stream& operator=(asio_write_stream&&) + noexcept(std::is_nothrow_move_assignable_v) = default; + + /// Returns the executor from the underlying stream. + executor_type get_executor() const { return stream_.get_executor(); } + + /// Initiates an async write, returning an IoAwaitable. + template + auto write_some(Seq buffers) + { + return stream_.async_write_some(as_asio_buffer_sequence(buffers), as_io_awaitable); + } + + /// Returns a reference to the underlying stream. + next_layer_type& next_layer() { return stream_; } + /// Returns a const reference to the underlying stream. + const next_layer_type& next_layer() const { return stream_; } + + private: + AsyncWriteStream stream_; +}; + +// Deduction guide +template +asio_write_stream(AsyncWriteStream) -> asio_write_stream; + + +/** Wraps an Asio AsyncStream for use with capy's Stream concept. + + Adapts an Asio stream (supporting both read and write) to provide + capy-style `read_some` and `write_some` operations that return + an IoAwaitable. + + @tparam AsyncStream The underlying Asio stream type + + @par Example + @code + boost::asio::ip::tcp::socket socket(io); + capy::asio_stream stream(std::move(socket)); + + // Read + auto read_result = co_await stream.read_some(read_buf); + + // Write + auto write_result = co_await stream.write_some(write_buf); + @endcode +*/ +template +struct asio_stream +{ + using executor_type = typename AsyncStream::executor_type; + using next_layer_type = AsyncStream; + + /// Default constructor. + asio_stream() = default; + + /// Construct from an Asio stream. + explicit asio_stream(AsyncStream stream) + noexcept(std::is_nothrow_move_constructible_v) + : stream_(std::move(stream)) + { + } + + asio_stream(const asio_stream&) + noexcept(std::is_nothrow_copy_constructible_v) = default; + asio_stream(asio_stream&&) + noexcept(std::is_nothrow_move_constructible_v) = default; + asio_stream& operator=(const asio_stream&) + noexcept(std::is_nothrow_copy_assignable_v) = default; + asio_stream& operator=(asio_stream&&) + noexcept(std::is_nothrow_move_assignable_v) = default; + + /// Returns the executor from the underlying stream. + executor_type get_executor() const { return stream_.get_executor(); } + + /// Initiates an async read, returning an IoAwaitable. + template + auto read_some(Seq buffers) + { + return stream_.async_read_some(as_asio_buffer_sequence(buffers), as_io_awaitable); + } + + /// Initiates an async write, returning an IoAwaitable. + template + auto write_some(Seq buffers) + { + return stream_.async_write_some(as_asio_buffer_sequence(buffers), as_io_awaitable); + } + + /// Returns a reference to the underlying stream. + next_layer_type& next_layer() { return stream_; } + /// Returns a const reference to the underlying stream. + const next_layer_type& next_layer() const { return stream_; } + + private: + AsyncStream stream_; +}; + +// Deduction guide +template +asio_stream(AsyncStream) -> asio_stream; + +} + +#endif //BOOST_CAPY_ASIO_STREAM_HPP + diff --git a/include/boost/capy/test/read_stream.hpp b/include/boost/capy/test/read_stream.hpp index d3114350..997f3531 100644 --- a/include/boost/capy/test/read_stream.hpp +++ b/include/boost/capy/test/read_stream.hpp @@ -160,7 +160,7 @@ class read_stream } io_result - await_resume() + await_resume() noexcept { // Empty buffer is a no-op regardless of // stream state or fuse. diff --git a/include/boost/capy/test/stream.hpp b/include/boost/capy/test/stream.hpp index 32078e67..6edf223a 100644 --- a/include/boost/capy/test/stream.hpp +++ b/include/boost/capy/test/stream.hpp @@ -237,7 +237,7 @@ class stream } io_result - await_resume() + await_resume() noexcept { if(buffer_empty(buffers_)) return {{}, 0}; diff --git a/include/boost/capy/test/write_stream.hpp b/include/boost/capy/test/write_stream.hpp index 6e36abd3..93c26a50 100644 --- a/include/boost/capy/test/write_stream.hpp +++ b/include/boost/capy/test/write_stream.hpp @@ -177,7 +177,7 @@ class write_stream } io_result - await_resume() + await_resume() noexcept { if(buffer_empty(buffers_)) return {{}, 0}; diff --git a/test/unit/asio.cpp b/test/unit/asio.cpp index 6d809489..42483d68 100644 --- a/test/unit/asio.cpp +++ b/test/unit/asio.cpp @@ -13,21 +13,33 @@ #include #if __has_include() #include - +#include +#include +#include +#include +#include #include #include #include #include +#include +#include #include #include + #include +#include +#include #include #include #include #include +#include + + #include #include #include @@ -35,6 +47,7 @@ #include "test_suite.hpp" + namespace boost { namespace capy { @@ -207,6 +220,80 @@ struct boost_asio_test static_assert(boost::asio:: is_const_buffer_sequence::value); } + void testStreamToAsio() + { + thread_pool tp; + + async_write_stream ws{test::write_stream(), tp.get_executor()}; + + std::atomic done{0}; + + boost::asio::async_write( + ws, + boost::asio::buffer("Test", 4), + [&](boost::system::error_code ec, std::size_t n) + { + BOOST_TEST(!ec); + BOOST_TEST_EQ(n, 4); + done ++; + }); + + BOOST_TEST_EQ(ws.next_layer().data(), "Test"); + + async_read_stream rs{test::read_stream(), tp.get_executor()}; + rs.next_layer().provide("foobar"); + + + char data[100]; + boost::asio::async_read( + rs, + boost::asio::buffer(data), + [&](boost::system::error_code ec, std::size_t n) + { + BOOST_TEST_EQ(ec, boost::capy::error::eof); + BOOST_TEST_EQ(n, 6); + BOOST_TEST_EQ(std::string_view(data, n), "foobar"); + done ++; + }); + + while (done.load() < 2u); + + tp.join(); + } + + void testStreamFromAsio() + { + boost::asio::io_context ctx; + boost::asio::readable_pipe rp{ctx}; + boost::asio::writable_pipe wp{ctx}; + boost::asio::connect_pipe(rp, wp); + + any_read_stream rs{asio_read_stream(std::move(rp)) }; + any_write_stream ws{asio_write_stream(std::move(wp))}; + + bool done = false; + + auto t = + [&]() -> task + { + std::string rb; + rb.resize(10); + + auto [r1, r2, r3] = co_await capy::when_all( + ws.write_some(make_buffer("hello pipe", 10)), + rs.read_some(make_buffer(rb)) + ); + + BOOST_TEST(!r1); + BOOST_TEST_EQ(r2, r3); + BOOST_TEST_EQ(rb, "hello pipe"); + }; + + run_async(wrap_asio_executor(ctx.get_executor()), [&]{done = true;})(t()); + + ctx.run(); + BOOST_TEST(done); + } void run() { @@ -216,6 +303,8 @@ struct boost_asio_test testAsIoAwaitable(); testAsioSpawn(); testTimer(); + testStreamToAsio(); + testStreamFromAsio(); } }; diff --git a/test/unit/asio_standalone.cpp b/test/unit/asio_standalone.cpp index ccec9ee5..4e299f27 100644 --- a/test/unit/asio_standalone.cpp +++ b/test/unit/asio_standalone.cpp @@ -11,20 +11,29 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include +#include +#include #include +#include #include #include #include +#include +#include +#include #include "test_helpers.hpp" #include "test_suite.hpp" @@ -200,6 +209,85 @@ struct asio_standalone_test static_assert(::asio::is_mutable_buffer_sequence::value); static_assert(::asio:: is_const_buffer_sequence::value); } + + + + void testStreamToAsio() + { + thread_pool tp; + + async_write_stream ws{test::write_stream(), tp.get_executor()}; + + std::atomic done{0}; + + ::asio::async_write( + ws, + ::asio::buffer("Test", 4), + [&](std::error_code ec, std::size_t n) + { + BOOST_TEST(!ec); + BOOST_TEST_EQ(n, 4); + done ++; + }); + + BOOST_TEST_EQ(ws.next_layer().data(), "Test"); + + async_read_stream rs{test::read_stream(), tp.get_executor()}; + rs.next_layer().provide("foobar"); + + + char data[100]; + ::asio::async_read( + rs, + ::asio::buffer(data), + [&](std::error_code ec, std::size_t n) + { + BOOST_TEST_EQ(ec, boost::capy::error::eof); + BOOST_TEST_EQ(n, 6); + BOOST_TEST_EQ(std::string_view(data, n), "foobar"); + done ++; + }); + + while (done.load() < 2u); + + tp.join(); + } + + + void testStreamFromAsio() + { + ::asio::io_context ctx; + ::asio::readable_pipe rp{ctx}; + ::asio::writable_pipe wp{ctx}; + ::asio::connect_pipe(rp, wp); + + any_read_stream rs{asio_read_stream(std::move(rp)) }; + any_write_stream ws{asio_write_stream(std::move(wp))}; + + bool done = false; + + auto t = + [&]() -> task + { + std::string rb; + rb.resize(10); + + auto [r1, r2, r3] = co_await capy::when_all( + ws.write_some(make_buffer("hello pipe", 10)), + rs.read_some(make_buffer(rb)) + ); + + BOOST_TEST(!r1); + BOOST_TEST_EQ(r2, r3); + BOOST_TEST_EQ(rb, "hello pipe"); + }; + + run_async(wrap_asio_executor(ctx.get_executor()), [&]{done = true;})(t()); + + ctx.run(); + BOOST_TEST(done); + } + void run() { @@ -209,6 +297,8 @@ struct asio_standalone_test testAsIoAwaitable(); testAsioSpawn(); testTimer(); + testStreamToAsio(); + testStreamFromAsio(); } }; From 95d7ea806f6cf7ba1c0e3d094da33a154efb18a3 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Tue, 21 Apr 2026 08:49:28 +0800 Subject: [PATCH 29/29] asio echo_server example --- example/asio/CMakeLists.txt | 132 ++-------------------------------- example/asio/Jamfile | 14 +--- example/asio/echo_server.cpp | 136 +++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 140 deletions(-) create mode 100644 example/asio/echo_server.cpp diff --git a/example/asio/CMakeLists.txt b/example/asio/CMakeLists.txt index 003e1c5e..96aa93f1 100644 --- a/example/asio/CMakeLists.txt +++ b/example/asio/CMakeLists.txt @@ -7,137 +7,13 @@ # Official repository: https://github.com/cppalliance/capy # -#------------------------------------------------- -# -# Find or fetch Boost.Asio -# -#------------------------------------------------- -if (NOT TARGET Boost::asio) - # Use BOOST_SRC_DIR if already defined, else check env, else use default - if (NOT DEFINED BOOST_SRC_DIR) - if (DEFINED ENV{BOOST_SRC_DIR}) - set(BOOST_SRC_DIR "$ENV{BOOST_SRC_DIR}" CACHE STRING "Boost source dir") - else () - set(BOOST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../.." CACHE STRING "Boost source dir") - endif () - endif () - - # Find absolute BOOST_SRC_DIR - if (NOT IS_ABSOLUTE ${BOOST_SRC_DIR}) - set(BOOST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_SRC_DIR}") - endif () - - # Validate BOOST_SRC_DIR - set(BOOST_SRC_DIR_IS_VALID ON) - foreach (F "CMakeLists.txt" "Jamroot" "boost-build.jam" "bootstrap.sh" "libs") - if (NOT EXISTS "${BOOST_SRC_DIR}/${F}") - set(BOOST_SRC_DIR_IS_VALID OFF) - break() - endif () - endforeach () - - if (BOOST_SRC_DIR_IS_VALID) - message(STATUS "Building with valid Boost src dir") - # From BOOST_SRC_DIR - set(BOOST_INCLUDE_LIBRARIES asio) - set(BOOST_EXCLUDE_LIBRARIES capy) - set(PREV_BUILD_TESTING ${BUILD_TESTING}) - set(BUILD_TESTING OFF CACHE BOOL "Build the tests." FORCE) - add_subdirectory(${BOOST_SRC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/_deps/boost EXCLUDE_FROM_ALL) - set(BUILD_TESTING ${PREV_BUILD_TESTING} CACHE BOOL "Build the tests." FORCE) - else () - # Try installed Boost package - find_package(Boost QUIET) - if (Boost_FOUND) - message(STATUS "Using installed Boost package for asio example") - if (NOT TARGET Boost::asio) - add_library(Boost::asio ALIAS Boost::headers) - endif () - else () - # Fallback: FetchContent - message(STATUS "No local Boost found, using FetchContent to download Boost") - include(FetchContent) - - # Policy CMP0135 (introduced in CMake 3.24) controls the timestamp behavior - # for files extracted from archives downloaded via FetchContent/ExternalProject. - # - # OLD behavior: Extracted files keep their original timestamps from the archive. - # This can cause build issues because archive timestamps are often - # older than dependent build files, triggering unnecessary rebuilds - # or causing "file has modification time in the future" warnings. - # - # NEW behavior: Extracted files get the current time (download time) as their - # timestamp, which avoids these timing-related build problems. - # - # We set NEW if the policy exists. On older CMake versions (< 3.24), the policy - # doesn't exist, so this block is skipped and FetchContent works normally. - # This avoids using DOWNLOAD_EXTRACT_TIMESTAMP directly in FetchContent_Declare, - # which would cause a parse error on CMake < 3.24 (unrecognized keyword). - if (POLICY CMP0135) - cmake_policy(SET CMP0135 NEW) - endif() - - set(BOOST_INCLUDE_LIBRARIES asio) - FetchContent_Declare( - boost - URL https://github.com/boostorg/boost/releases/download/boost-1.88.0/boost-1.88.0-cmake.tar.xz - ) - set(PREV_BUILD_TESTING ${BUILD_TESTING}) - set(BUILD_TESTING OFF CACHE BOOL "Build the tests." FORCE) - FetchContent_MakeAvailable(boost) - set(BUILD_TESTING ${PREV_BUILD_TESTING} CACHE BOOL "Build the tests." FORCE) - endif () - endif () -endif () - -#------------------------------------------------- -# -# Example executables -# -#------------------------------------------------- -file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp - CMakeLists.txt - Jamfile) - -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) - -# any_stream example (coroutine-based) -add_executable(capy_example_any_stream - any_stream.cpp - api/capy_streams.cpp - api/capy_streams.hpp) - -set_property(TARGET capy_example_any_stream - PROPERTY FOLDER "examples") - -target_link_libraries(capy_example_any_stream - Boost::capy - Boost::asio) - -# asio_callbacks example (callback-based with uni_stream) -add_executable(capy_example_asio_callbacks - asio_callbacks.cpp - api/capy_streams.cpp - api/capy_streams.hpp - api/uni_stream.hpp) - -set_property(TARGET capy_example_asio_callbacks - PROPERTY FOLDER "examples") - -target_link_libraries(capy_example_asio_callbacks - Boost::capy - Boost::asio) - # use_capy example (use_capy completion token) -add_executable(capy_example_use_capy - use_capy_example.cpp - api/capy_streams.cpp - api/capy_streams.hpp - api/use_capy.hpp) +add_executable(capy_example_asio_echo_server + echo_server.cpp) -set_property(TARGET capy_example_use_capy +set_property(TARGET capy_example_asio_echo_server PROPERTY FOLDER "examples") -target_link_libraries(capy_example_use_capy +target_link_libraries(capy_example_asio_echo_server Boost::capy Boost::asio) diff --git a/example/asio/Jamfile b/example/asio/Jamfile index ca138e35..935e6d87 100644 --- a/example/asio/Jamfile +++ b/example/asio/Jamfile @@ -14,17 +14,7 @@ project . ; -exe any_stream : - any_stream.cpp - api/capy_streams.cpp +exe echo_server : + echo_server.cpp ; -exe asio_callbacks : - asio_callbacks.cpp - api/capy_streams.cpp - ; - -exe use_capy_example : - use_capy_example.cpp - api/capy_streams.cpp - ; diff --git a/example/asio/echo_server.cpp b/example/asio/echo_server.cpp new file mode 100644 index 00000000..f14494e1 --- /dev/null +++ b/example/asio/echo_server.cpp @@ -0,0 +1,136 @@ +// +// Copyright (c) 2026 Klemens 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) +// +// Official repository: https://github.com/cppalliance/capy +// + +// +// Echo Server Example (Boost.Asio) +// +// A complete echo server using Boost.Asio TCP sockets with capy coroutines. +// Demonstrates how to use capy::as_io_awaitable to await ASIO async operations. +// + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace net = boost::asio; +namespace capy = boost::capy; + +// Socket type with as_io_awaitable as the default completion token. +// This allows omitting the completion token in async calls. +using tcp_socket = capy::as_io_awaitable_t::as_default_on_t< + net::ip::tcp::socket>; + +using tcp_acceptor = capy::as_io_awaitable_t::as_default_on_t< + net::ip::tcp::acceptor>; + +// Handle a single client session. +// Reads data from the socket and echoes it back until the connection closes. +capy::io_task<> echo_session(tcp_socket sock) +{ + char buf[1024]; + + for (;;) + { + // Read some data from the client. + auto res = co_await sock.async_read_some(net::buffer(buf)); + + if (boost::system::error_code ec = res.ec; ec) + { + if (ec != net::error::eof) + std::cerr << "Read error: " << ec.message() << "\n"; + break; + } + + // Write the data back to the client. + res = co_await net::async_write( + sock, net::buffer(buf, std::get<0>(res.values))); + + if (res.ec) + { + std::cerr << "Write error: " << res.ec.message() << "\n"; + break; + } + } + + boost::system::error_code ec; + sock.shutdown(net::ip::tcp::socket::shutdown_both, ec); + sock.close(ec); + + co_return {}; +} + +// Accept loop - accepts connections and spawns echo sessions. +capy::io_task<> accept_loop(tcp_acceptor& acceptor) +{ + auto exec = capy::wrap_asio_executor(acceptor.get_executor()); + auto ep = acceptor.local_endpoint(); + std::cout << "Listening on port " << ep.port() << "\n"; + + for (;;) + { + // Accept a new connection. + auto [ec, sock] = co_await acceptor.async_accept(); + + if (ec) + { + std::cerr << "Accept error: " << ec.message() << "\n"; + continue; + } + + auto remote = sock.remote_endpoint(); + std::cout << "Connection from " << remote.address() + << ":" << remote.port() << "\n"; + + // Spawn an echo session for this connection. + // Convert the socket to use as_io_awaitable as default. + capy::run_async(exec)( + echo_session(capy::as_io_awaitable_t::as_default_on(std::move(sock)))); + } +} + +int main(int argc, char* argv[]) +{ + unsigned short port = 8080; + if (argc > 1) + port = static_cast(std::atoi(argv[1])); + + try + { + net::io_context ioc; + + // Create the acceptor with as_io_awaitable as default. + tcp_acceptor acceptor( + ioc, + net::ip::tcp::endpoint(net::ip::tcp::v4(), port)); + + // Wrap the ASIO executor for use with capy. + auto exec = capy::wrap_asio_executor(ioc.get_executor()); + + // Spawn the accept loop. + capy::run_async(exec)(accept_loop(acceptor)); + + // Run the I/O context. + ioc.run(); + } + catch (std::exception const& e) + { + std::cerr << "Error: " << e.what() << "\n"; + return 1; + } + + return 0; +}