From 3b369d38c12680609221423d20a95ffbd6d23fbc Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 30 Mar 2026 21:43:11 +0800 Subject: [PATCH 01/24] 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 ef070f9ac..4911cae1b 100644 --- a/test/unit/test_helpers.hpp +++ b/test/unit/test_helpers.hpp @@ -71,8 +71,9 @@ struct test_executor { int id_ = 0; int* dispatch_count_ = nullptr; + int* work_count_ = nullptr; test_io_context* ctx_ = nullptr; - + test_executor() = default; explicit @@ -87,12 +88,26 @@ struct test_executor { } + explicit + test_executor(int& count, int & work) noexcept + : dispatch_count_(&count) + , work_count_(&work) + { + } + test_executor(int id, int& count) noexcept : id_(id) , dispatch_count_(&count) { } + test_executor(int id, int& count, int& work) noexcept + : id_(id) + , dispatch_count_(&count) + , work_count_(&work) + { + } + bool operator==(test_executor const& other) const noexcept { @@ -104,8 +119,8 @@ struct test_executor test_io_context& context() const noexcept; - void on_work_started() const noexcept {} - void on_work_finished() const noexcept {} + void on_work_started() const noexcept {if (work_count_) (*work_count_)++;} + void on_work_finished() const noexcept {if (work_count_) (*work_count_)--;} std::coroutine_handle<> dispatch(continuation& c) const From c179c1eadedea68e54be1a169aff6a4335061e97 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Mon, 30 Mar 2026 21:44:06 +0800 Subject: [PATCH 02/24] 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 000000000..a68e9332d --- /dev/null +++ b/include/boost/capy/asio/executor_adapter.hpp @@ -0,0 +1,298 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail +{ + +struct asio_adapter_context_service + : execution_context::service, + // shutdown is protected + boost::asio::execution_context +{ + asio_adapter_context_service(boost::capy::execution_context & ctx) {} + void shutdown() override {boost::asio::execution_context::shutdown();} +}; + +} + + +template, + typename Blocking = boost::asio::execution::blocking_t::possibly_t, + typename Outstanding = boost::asio::execution::outstanding_work_t::untracked_t> +struct asio_executor_adapter +{ + template + asio_executor_adapter(const asio_executor_adapter & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_), allocator_(rhs.allocator_) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + template + asio_executor_adapter(asio_executor_adapter && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)), allocator_(std::move(rhs.allocator_)) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + asio_executor_adapter(Executor executor, const Allocator & alloc) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)), allocator_(alloc) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + asio_executor_adapter(Executor executor) noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)), allocator_(executor_.context().get_frame_allocator()) + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_started(); + } + + ~asio_executor_adapter() + { + if constexpr(Outstanding() == boost::asio::execution::outstanding_work.tracked) + executor_.on_work_finished(); + } + + template + asio_executor_adapter & operator=(const asio_executor_adapter & rhs) + { + + if constexpr (Outstanding_() == boost::asio::execution::outstanding_work.tracked) + if (rhs.executor_ != executor_) + { + rhs.executor_.on_work_started(); + executor_.on_work_finished(); + } + + executor_ = rhs.executor_; + allocator_ = rhs.allocator_; + } + + bool operator==(const asio_executor_adapter & rhs) const noexcept + { + return executor_ == rhs.executor_ + && allocator_ == rhs.allocator_; + } + bool operator!=(const asio_executor_adapter & rhs) const noexcept + { + return executor_ != rhs.executor_ + && allocator_ != rhs.allocator_; + } + + boost::asio::execution_context& + query(boost::asio::execution::context_t) const noexcept + { + return context(); + } + + constexpr boost::asio::execution::blocking_t + query(boost::asio::execution::blocking_t) const noexcept + { + return Blocking(); + } + + constexpr asio_executor_adapter + require(boost::asio::execution::blocking_t::possibly_t) const + { + return *this; + } + + constexpr asio_executor_adapter + require(boost::asio::execution::blocking_t::never_t) const + { + return *this; + } + + constexpr asio_executor_adapter + require(boost::asio::execution::blocking_t::always_t) const + { + return *this; + } + + static constexpr boost::asio::execution::outstanding_work_t query( + boost::asio::execution::outstanding_work_t) noexcept + { + return Outstanding(); + } + + constexpr asio_executor_adapter + require(boost::asio::execution::outstanding_work_t::tracked_t) const + { + return *this; + } + + constexpr asio_executor_adapter + require(boost::asio::execution::outstanding_work_t::untracked_t) const + { + return *this; + } + + + template + constexpr Allocator query( + boost::asio::execution::allocator_t) const noexcept + { + return allocator_; + } + template + constexpr asio_executor_adapter + require(boost::asio::execution::allocator_t a) const + { + return asio_executor_adapter( + executor_, a.value() + ); + } + + boost::asio::execution_context & context() const noexcept + { + return executor_.context().template use_service(); + } + + template + void execute(Function&& f) const + { + constexpr static boost::asio::execution::blocking_t b; + if constexpr (Blocking() == b.never) + executor_.post(make_handle_(std::forward(f)).cont); + else if constexpr(Blocking() == b.possibly) + executor_.dispatch(make_handle_(std::forward(f)).cont).resume(); + else if constexpr(Blocking() == b.always) + std::forward(f)(); + } + + + private: + + struct handler_promise_base_empty_ {}; + struct handler_promise_base_ + { + using alloc_t = std::allocator_traits::template rebind_alloc; + template + void * operator new(std::size_t n, const asio_executor_adapter & adapter, Func &) + { + alloc_t alloc(adapter.allocator_); + std::size_t m = n; + if (n % alignof(alloc_t) > 0) + m += alignof(alloc_t) - (n % alignof(alloc_t)); + + char * mem = alloc.allocate(m + sizeof(alloc)); + + new (mem + m) alloc_t(std::move(alloc)); + return mem; + } + + void operator delete(void * p, std::size_t n) + { + std::size_t m = n; + if (n % alignof(alloc_t) > 0) + m += alignof(alloc_t) - (n % alignof(alloc_t)); + + auto * a = reinterpret_cast(static_cast(p) + m); + + alloc_t alloc(std::move(*a)); + a->~alloc_t(); + + alloc.deallocate(static_cast(p), n); + } + + }; + + struct handler_promise_ : std::conditional_t< + std::same_as>, + handler_promise_base_empty_, + handler_promise_base_> + { + std::suspend_always initial_suspend() const noexcept {return {};} + std::suspend_never final_suspend() const noexcept {return {};} + + template + auto yield_value(Function & func) + { + struct yielder + { + Function func; + + bool await_ready() const {return false;} + void await_suspend(std::coroutine_handle<> h) + { + auto f = std::move(func); + h.destroy(); + std::move(f)(); + } + void await_resume() {} + }; + + return yielder{std::move(func)}; + } + + continuation cont; + + void unhandled_exception() { throw; } + continuation & get_return_object() + { + cont.h = std::coroutine_handle::from_promise(*this); + cont.next = nullptr; + return cont; + } + }; + + struct helper_ + { + capy::continuation &cont; + helper_(continuation & cont) noexcept : cont(cont) {} + using promise_type = handler_promise_; + }; + + template + helper_ make_handle_(Function func) const + { + co_yield func; + } + + template + friend struct asio_executor_adapter; + Executor executor_; + [[no_unique_address]] Allocator allocator_; + +}; + + + + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/include/boost/capy/asio/standalone_adapter_standalone.hpp b/include/boost/capy/asio/standalone_adapter_standalone.hpp new file mode 100644 index 000000000..5c437ebcf --- /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 000000000..82bd6d3bd --- /dev/null +++ b/test/unit/asio.cpp @@ -0,0 +1,143 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#if __has_include() +#include + +#include +#include +#include +#include +#endif + +#if __has_include() +#include + +#include +#include +#include +#include + +#endif + + +#include +#include "test_helpers.hpp" +#include "test_suite.hpp" + +#include + +namespace boost { +namespace capy { + + +#if __has_include() + +struct asio_standalone_test +{ + void testExecutor() + { + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::standalone_asio_executor_adapter wrapped_te{exec}; + + bool ran = false; + ::asio::post(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 0); + BOOST_TEST(ran); + + + ran = false; + ::asio::dispatch(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 1); + BOOST_TEST(ran); + + BOOST_TEST(work_cnt == 0); + { + auto wk = ::asio::require(wrapped_te, ::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + + } + BOOST_TEST_EQ(work_cnt, 0); + ::asio::any_io_executor aio{wrapped_te}; + BOOST_TEST_EQ(work_cnt, 0); + aio = ::asio::prefer(aio, ::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + aio = nullptr; + BOOST_TEST_EQ(work_cnt, 0); + } + + void run() + { + testExecutor(); + } +}; + +#endif + +#if __has_include() + +struct boost_asio_test +{ + void testExecutor() + { + int dispatch_count = 0; + int work_cnt = 0; + test_executor exec{0, dispatch_count, work_cnt}; + boost::capy::asio_executor_adapter wrapped_te{exec}; + + bool ran = false; + boost::asio::post(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 0); + BOOST_TEST(ran); + + + ran = false; + boost::asio::dispatch(wrapped_te, [&]{ran = true;}); + BOOST_TEST_EQ(dispatch_count, 1); + BOOST_TEST(ran); + + BOOST_TEST(work_cnt == 0); + { + auto wk = boost::asio::require(wrapped_te, boost::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + + } + BOOST_TEST_EQ(work_cnt, 0); + boost::asio::any_io_executor aio{wrapped_te}; + BOOST_TEST_EQ(work_cnt, 0); + aio = boost::asio::prefer(aio, boost::asio::execution::outstanding_work.tracked); + BOOST_TEST_EQ(work_cnt, 1); + aio = nullptr; + BOOST_TEST_EQ(work_cnt, 0); + } + + void run() + { + testExecutor(); + } +}; + +#endif + +#if __has_include() +TEST_SUITE( + asio_standalone_test, + "boost.capy.asio.standalone"); +#endif + +#if __has_include() +TEST_SUITE( + boost_asio_test, + "boost.capy.asio.boost"); +#endif + +} // namespace capy +} // namespace boost From ebd79dfee6a48de3bc5d9d8bd9c570ed2a61346c Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Tue, 31 Mar 2026 07:50:08 +0800 Subject: [PATCH 03/24] 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 000000000..f1ac3e8c1 --- /dev/null +++ b/include/boost/capy/asio/detail/asio_context_service.hpp @@ -0,0 +1,37 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_DETAIL_ASIO_CONTEXT_SERVICE +#define BOOST_CAPY_ASIO_DETAIL_ASIO_CONTEXT_SERVICE + +#include + +namespace boost::capy::detail +{ + +template +struct asio_context_service + : Context::service + , capy::execution_context +{ + static Context::id id; + + asio_context_service(Context & ctx) + : Context::service(ctx) {} + void shutdown() override {capy::execution_context::shutdown();} +}; + + +// asio_context_service is templated for this id. +template +Context::id asio_context_service::id; + +} + +#endif diff --git a/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp new file mode 100644 index 000000000..d2da7ce97 --- /dev/null +++ b/include/boost/capy/asio/detail/asio_coroutine_unique_handle.hpp @@ -0,0 +1,49 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + + +#ifndef BOOST_CAPY_ASIO_DETAIL_ASIO_COROUTINE_UNIQUE_HANDLE +#define BOOST_CAPY_ASIO_DETAIL_ASIO_COROUTINE_UNIQUE_HANDLE + +#include +#include + +namespace boost::capy::detail +{ + +struct asio_coroutine_unique_handle +{ + struct deleter + { + deleter() = default; + void operator()(void * h) const + { + std::coroutine_handle::from_address(h).destroy(); + } + }; + std::unique_ptr handle; + + asio_coroutine_unique_handle( + std::coroutine_handle h) : handle(h.address()) {} + + asio_coroutine_unique_handle( + asio_coroutine_unique_handle && + ) noexcept = default; + + void operator()() + { + std::coroutine_handle::from_address( + handle.release() + ).resume(); + } +}; + +} + +#endif diff --git a/include/boost/capy/asio/detail/completion_handler.hpp b/include/boost/capy/asio/detail/completion_handler.hpp new file mode 100644 index 000000000..c53c2f8ba --- /dev/null +++ b/include/boost/capy/asio/detail/completion_handler.hpp @@ -0,0 +1,134 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER +#define BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace boost::capy::detail +{ + + +struct asio_immediate_executor_helper +{ + enum completed_immediately_t + { + no, maybe, yes, initiating + }; + + asio_executor_adapter exec; + completed_immediately_t * completed_immediately = nullptr; + + template + void execute(Fn && fn) const + { + // only allow it when we're still initializing + if (completed_immediately && + ((*completed_immediately == initiating) + || (*completed_immediately == maybe))) + { + // only use this indicator if the fn will actually call our completion-handler + // otherwise this was a single op in a composed operation + *completed_immediately = maybe; + fn(); + + if (*completed_immediately != yes) + *completed_immediately = initiating; + } + else + { + boost::asio::post(exec, std::forward(fn)); + } + } + + friend bool operator==(const asio_immediate_executor_helper& lhs, + const asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec == rhs.exec; + } + + friend bool operator!=(const asio_immediate_executor_helper& lhs, + const asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec != rhs.exec; + } + + asio_immediate_executor_helper(const asio_immediate_executor_helper & rhs) noexcept = default; + asio_immediate_executor_helper(executor_ref inner, completed_immediately_t * completed_immediately) + : exec(std::move(inner)), completed_immediately(completed_immediately) + { + } +}; + + +template +struct asio_coroutine_completion_handler +{ + struct deleter + { + deleter() = default; + void operator()(void * h) const + { + std::coroutine_handle::from_address(h).destroy(); + } + }; + asio_coroutine_unique_handle handle; + std::optional> & result; + capy::io_env * env; + boost::asio::cancellation_slot slot; + asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; + + using allocator_type = std::pmr::polymorphic_allocator; + allocator_type get_allocator() const {return env->frame_allocator;} + + using executor_type = asio_executor_adapter; + executor_type get_executor() const {return env->executor;} + + using cancellation_slot_type = boost::asio::cancellation_slot; + cancellation_slot_type get_cancellation_slot() const {return slot;} + + using immediate_executor_type = asio_immediate_executor_helper; + immediate_executor_type get_immediate_executor() const + { + return immediate_executor_type{env->executor, completed_immediately }; + }; + + asio_coroutine_completion_handler( + std::coroutine_handle h, + std::optional> & result, + capy::io_env * env, + boost::asio::cancellation_slot slot = {}, + asio_immediate_executor_helper::completed_immediately_t * ci = nullptr) + : handle(h), result(result), env(env), slot(slot), completed_immediately(ci) {} + + asio_coroutine_completion_handler( + asio_coroutine_completion_handler && + ) noexcept = default; + + void operator()(Args ... args) + { + result.emplace(std::forward(args)...); + std::move(handle)(); + } +}; + +} + +#endif //BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER diff --git a/include/boost/capy/asio/detail/standalone_completion_handler.hpp b/include/boost/capy/asio/detail/standalone_completion_handler.hpp new file mode 100644 index 000000000..1f0055179 --- /dev/null +++ b/include/boost/capy/asio/detail/standalone_completion_handler.hpp @@ -0,0 +1,134 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_DETAIL_STANDALONE_COMPLETION_HANDLER +#define BOOST_CAPY_ASIO_DETAIL_STANDALONE_COMPLETION_HANDLER + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace boost::capy::detail +{ + + +struct standalone_asio_immediate_executor_helper +{ + enum completed_immediately_t + { + no, maybe, yes, initiating + }; + + standalone_asio_executor_adapter exec; + completed_immediately_t * completed_immediately = nullptr; + + template + void execute(Fn && fn) const + { + // only allow it when we're still initializing + if (completed_immediately && + ((*completed_immediately == initiating) + || (*completed_immediately == maybe))) + { + // only use this indicator if the fn will actually call our completion-handler + // otherwise this was a single op in a composed operation + *completed_immediately = maybe; + fn(); + + if (*completed_immediately != yes) + *completed_immediately = initiating; + } + else + { + ::asio::post(exec, std::forward(fn)); + } + } + + friend bool operator==(const standalone_asio_immediate_executor_helper& lhs, + const standalone_asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec == rhs.exec; + } + + friend bool operator!=(const standalone_asio_immediate_executor_helper& lhs, + const standalone_asio_immediate_executor_helper& rhs) noexcept + { + return lhs.exec == rhs.exec; + } + + standalone_asio_immediate_executor_helper(const standalone_asio_immediate_executor_helper & rhs) noexcept = default; + standalone_asio_immediate_executor_helper(executor_ref inner, completed_immediately_t * completed_immediately) + : exec(std::move(inner)), completed_immediately(completed_immediately) + { + } +}; + + +template +struct standalone_asio_coroutine_completion_handler +{ + struct deleter + { + deleter() = default; + void operator()(void * h) const + { + std::coroutine_handle::from_address(h).destroy(); + } + }; + asio_coroutine_unique_handle handle; + std::optional> & result; + capy::io_env * env; + ::asio::cancellation_slot slot; + standalone_asio_immediate_executor_helper::completed_immediately_t * completed_immediately = nullptr; + + using allocator_type = std::pmr::polymorphic_allocator; + allocator_type get_allocator() const {return env->frame_allocator;} + + using executor_type = standalone_asio_executor_adapter; + executor_type get_executor() const {return env->executor;} + + using cancellation_slot_type = ::asio::cancellation_slot; + cancellation_slot_type get_cancellation_slot() const {return slot;} + + using immediate_executor_type = standalone_asio_immediate_executor_helper; + immediate_executor_type get_immediate_executor() const + { + return immediate_executor_type{env->executor, completed_immediately }; + }; + + standalone_asio_coroutine_completion_handler( + std::coroutine_handle h, + std::optional> & result, + capy::io_env * env, + ::asio::cancellation_slot slot = {}, + standalone_asio_immediate_executor_helper::completed_immediately_t * ci = nullptr) + : handle(h), result(result), env(env), slot(slot), completed_immediately(ci) {} + + standalone_asio_coroutine_completion_handler( + standalone_asio_coroutine_completion_handler && + ) noexcept = default; + + void operator()(Args ... args) + { + result.emplace(std::forward(args)...); + std::move(handle)(); + } +}; + +} + +#endif //BOOST_CAPY_ASIO_DETAIL_COMPLETION_HANDLER diff --git a/include/boost/capy/asio/executor_from_asio.hpp b/include/boost/capy/asio/executor_from_asio.hpp new file mode 100644 index 000000000..e7f7a88f3 --- /dev/null +++ b/include/boost/capy/asio/executor_from_asio.hpp @@ -0,0 +1,227 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_EXECUTOR_FROM_ASIO_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_FROM_ASIO_HPP + +#include +#include +#include + +#include +#include +#include + +namespace boost { +namespace capy { + + + +template + requires requires (Executor exec) + { + { + boost::asio::prefer( + std::move(exec), + boost::asio::execution::outstanding_work.tracked + ) + } -> std::convertible_to; + { + boost::asio::prefer( + std::move(exec), + boost::asio::execution::outstanding_work.untracked + ) + } -> std::convertible_to; + } +struct executor_from_asio_properties +{ + executor_from_asio_properties(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_asio_properties(executor_from_asio_properties && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_asio_properties(const executor_from_asio_properties & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + auto & ec = boost::asio::query(executor_, boost::asio::execution::context); + return boost::asio::use_service>(ec); + } + + void on_work_started() const noexcept + { + + using boost::asio::execution::outstanding_work; + if (boost::asio::query(executor_, outstanding_work) == outstanding_work.untracked) + executor_ = boost::asio::prefer( + std::move(executor_), outstanding_work.tracked); + } + + void on_work_finished() const noexcept + { + using boost::asio::execution::outstanding_work; + if (boost::asio::query(executor_, outstanding_work) == outstanding_work.tracked) + executor_ = boost::asio::prefer( + std::move(executor_), outstanding_work.untracked); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + boost::asio::dispatch( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + boost::asio::post( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + } + + bool operator==(const executor_from_asio_properties & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_asio_properties & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + mutable Executor executor_; +}; + + +template + requires requires (Executor exec) + { + exec.on_work_started(); + exec.on_work_finished(); + } +struct executor_from_asio_net_ts +{ + executor_from_asio_net_ts(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_asio_net_ts(executor_from_asio_net_ts && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_asio_net_ts(const executor_from_asio_net_ts & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + return boost::asio::use_service> + ( + executor_.context() + ); + } + + void on_work_started() const noexcept + { + executor_.on_work_started(); + } + + void on_work_finished() const noexcept + { + executor_.on_work_finished(); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + executor_.dispatch( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + executor_.post( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + } + + bool operator==(const executor_from_asio_net_ts & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_asio_net_ts & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + Executor executor_; +}; + + +namespace detail +{ + + +struct executor_from_asio_net_ts_helper +{ + template + using impl = executor_from_asio_net_ts; +}; + +struct executor_from_asio_properties_helper +{ + template + using impl = executor_from_asio_properties; +}; + +template +using executor_from_asio_helper = + std::conditional_t< + requires (Executor exec) {{exec.on_work_started()};}, + executor_from_asio_net_ts_helper, + executor_from_asio_properties_helper> + ::template impl; + +} + +template +struct executor_from_asio : detail::executor_from_asio_helper +{ + using detail::executor_from_asio_helper::executor_from_asio_helper; +}; + +template +executor_from_asio(Executor) -> executor_from_asio; + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/include/boost/capy/asio/executor_from_standalone_asio.hpp b/include/boost/capy/asio/executor_from_standalone_asio.hpp new file mode 100644 index 000000000..b10adfb1e --- /dev/null +++ b/include/boost/capy/asio/executor_from_standalone_asio.hpp @@ -0,0 +1,228 @@ +// +// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASIO_EXECUTOR_FROM_STANDALONE_ASIO_HPP +#define BOOST_CAPY_ASIO_EXECUTOR_FROM_STANDALONE_ASIO_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace boost { +namespace capy { + + + +template + requires requires (Executor exec) + { + { + ::asio::prefer( + std::move(exec), + ::asio::execution::outstanding_work.tracked + ) + } -> std::convertible_to; + { + ::asio::prefer( + std::move(exec), + ::asio::execution::outstanding_work.untracked + ) + } -> std::convertible_to; + } +struct executor_from_standalone_asio_properties +{ + executor_from_standalone_asio_properties(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_standalone_asio_properties(executor_from_standalone_asio_properties && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_standalone_asio_properties(const executor_from_standalone_asio_properties & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + auto & ec = ::asio::query(executor_, ::asio::execution::context); + return ::asio::use_service>(ec); + } + + void on_work_started() const noexcept + { + + using ::asio::execution::outstanding_work; + if (::asio::query(executor_, outstanding_work) == outstanding_work.untracked) + executor_ = ::asio::prefer( + std::move(executor_), outstanding_work.tracked); + } + + void on_work_finished() const noexcept + { + using ::asio::execution::outstanding_work; + if (::asio::query(executor_, outstanding_work) == outstanding_work.tracked) + executor_ = ::asio::prefer( + std::move(executor_), outstanding_work.untracked); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + ::asio::dispatch( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + ::asio::post( + executor_, + detail::asio_coroutine_unique_handle(c.h) + ); + } + + bool operator==(const executor_from_standalone_asio_properties & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_standalone_asio_properties & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + mutable Executor executor_; +}; + + +template + requires requires (Executor exec) + { + exec.on_work_started(); + exec.on_work_finished(); + } +struct executor_from_standalone_asio_net_ts +{ + executor_from_standalone_asio_net_ts(Executor executor) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(executor)) + { + } + executor_from_standalone_asio_net_ts(executor_from_standalone_asio_net_ts && rhs) + noexcept(std::is_nothrow_move_constructible_v) + : executor_(std::move(rhs.executor_)) + { + } + executor_from_standalone_asio_net_ts(const executor_from_standalone_asio_net_ts & rhs) + noexcept(std::is_nothrow_copy_constructible_v) + : executor_(rhs.executor_) + { + } + + execution_context& context() const noexcept + { + return ::asio::use_service> + ( + executor_.context() + ); + } + + void on_work_started() const noexcept + { + executor_.on_work_started(); + } + + void on_work_finished() const noexcept + { + executor_.on_work_finished(); + } + + std::coroutine_handle<> dispatch(continuation & c) const + { + executor_.dispatch( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + + return std::noop_coroutine(); + } + + void post(continuation & c) const + { + executor_.post( + detail::asio_coroutine_unique_handle(c.h), + std::pmr::polymorphic_allocator( + boost::capy::get_current_frame_allocator())); + } + + bool operator==(const executor_from_standalone_asio_net_ts & rhs) const noexcept + { + return executor_ == rhs.executor_; + } + bool operator!=(const executor_from_standalone_asio_net_ts & rhs) const noexcept + { + return executor_ != rhs.executor_; + } + + private: + Executor executor_; +}; + + +namespace detail +{ + + +struct executor_from_standalone_asio_net_ts_helper +{ + template + using impl = executor_from_standalone_asio_net_ts; +}; + +struct executor_from_standalone_asio_properties_helper +{ + template + using impl = executor_from_standalone_asio_properties; +}; + +template +using executor_from_standalone_asio_helper = + std::conditional_t< + requires (Executor exec) {{exec.on_work_started()};}, + executor_from_standalone_asio_net_ts_helper, + executor_from_standalone_asio_properties_helper> + ::template impl; + +} + +template +struct executor_from_standalone_asio : detail::executor_from_standalone_asio_helper +{ + using detail::executor_from_standalone_asio_helper::executor_from_standalone_asio_helper; +}; + +template +executor_from_standalone_asio(Executor) -> executor_from_standalone_asio; + +} +} + + +#endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP diff --git a/include/boost/capy/asio/standalone_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 5c437ebcf..978e39491 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 4b44643e2fca50a2c6eca61e21b6cef7583b4dcc Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 2 Apr 2026 07:09:21 +0800 Subject: [PATCH 04/24] 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 000000000..dd83ab68e --- /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 000000000..981f00e17 --- /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 c53c2f8ba..dbf415ecb 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 1f0055179..21c710cfe 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 000000000..4a65932a6 --- /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 4627de1890dea77b30ea75f996a1d9c3cc56b9dd Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 3 Apr 2026 07:35:59 +0800 Subject: [PATCH 05/24] 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 dbf415ecb..ebc779ae1 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 000000000..3510dc667 --- /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 a68e9332d..594b116fa 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 000000000..da8994e57 --- /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 000000000..fb5567287 --- /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 82bd6d3bd..88b1f0905 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 7e8b40134bb29d9846910ff096f2fc30dd9a5439 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 15 Apr 2026 18:10:36 +0800 Subject: [PATCH 06/24] 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 dd83ab68e..15ab56e0e 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 000000000..f37bf18e3 --- /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 981f00e17..000000000 --- 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 ebc779ae1..db91b50d0 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 000000000..556ba7925 --- /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 000000000..73647b6e5 --- /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 21c710cfe..000000000 --- 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 594b116fa..ea179e790 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 e7f7a88f3..17108ffdc 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 b10adfb1e..000000000 --- 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 da8994e57..082f8bb1f 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 000000000..1c547c1c9 --- /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 4a65932a6..000000000 --- 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 978e39491..000000000 --- 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 fb5567287..000000000 --- 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 068fdb852..7e8266030 100644 --- a/include/boost/capy/ex/run_async.hpp +++ b/include/boost/capy/ex/run_async.hpp @@ -418,6 +418,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) @@ -490,7 +500,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 14774345b..24b362058 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 88b1f0905..83125e4bb 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 000000000..915d2f05c --- /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 000000000..58c45a3d9 --- /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 393be93c85c39484454a496ed3792d0da643d85d Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 15 Apr 2026 19:24:04 +0800 Subject: [PATCH 07/24] 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 15ab56e0e..99be89eba 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 f37bf18e3..8015084e7 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 f1ac3e8c1..271231b01 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 d2da7ce97..f198b00dc 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 db91b50d0..d2ab43cb8 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 3510dc667..b98d428b2 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 556ba7925..d00ee2ca5 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 73647b6e5..e778a8f03 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 ea179e790..354c3c43d 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 17108ffdc..56dddafb3 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 082f8bb1f..896511105 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 1c547c1c9..b3c828bf8 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 9bdb969f00f04b9a8d6116bde085819dab4bac68 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 15 Apr 2026 22:11:05 +0800 Subject: [PATCH 08/24] 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 8015084e7..b33442ea3 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 b3c828bf8..9cd13b107 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 915d2f05c..3ac526896 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 4dcd5fc2b6312954446feda11eddb143d6b4ab39 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 05:57:18 +0800 Subject: [PATCH 09/24] 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 7e8266030..068fdb852 100644 --- a/include/boost/capy/ex/run_async.hpp +++ b/include/boost/capy/ex/run_async.hpp @@ -418,16 +418,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) @@ -500,7 +490,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 24b362058..5a0c31c85 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 56ab948bbe076eebaaf584251b7231710b1d4310 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 06:22:39 +0800 Subject: [PATCH 10/24] 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 b33442ea3..3136d3a10 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 d00ee2ca5..79e786986 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 354c3c43d..f8efc096a 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 9cd13b107..50e8cb81e 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 3ac526896..76ceee276 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 71e948ea8c7205b935171c0da8ac11e2a7b51d41 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 06:43:02 +0800 Subject: [PATCH 11/24] 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 76ceee276..3c942e720 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 237aba0b4d44d58eafeb4e746e0893d76ca20fee Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 07:13:42 +0800 Subject: [PATCH 12/24] [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 6f070d251..fe51d9408 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 000000000..df8c9c99d --- /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 99be89eba..9795bb0e2 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 3136d3a10..4f106ea49 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 f8efc096a..6b34978c2 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 56dddafb3..948e0584e 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 896511105..cadb66cf6 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 50e8cb81e..ff68797c3 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 f44f3a117b965da894440f2a77966bd8d617f1ab Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 07:25:59 +0800 Subject: [PATCH 13/24] 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 4f106ea49..1669fe6e5 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 ff68797c3..37502aafe 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 0063bef471387b8f078a73b88dc9992d9aafa6c4 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 07:29:51 +0800 Subject: [PATCH 14/24] 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 79e786986..72e38c038 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 b9de500eae93df117da7c5dcc0dbf887cb3bc248 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 15:35:17 +0800 Subject: [PATCH 15/24] 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 e778a8f03..2ff246c45 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 6a1a49e9e3e4bf17b47cb32fc54bb53aa49d579f Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 16 Apr 2026 22:07:18 +0800 Subject: [PATCH 16/24] 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 1669fe6e5..d90dd4328 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 37502aafe..9ee2f5b0d 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 f30bf0bd9d0ad947c9274717720a5bd2a492ce4f Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 05:48:07 +0800 Subject: [PATCH 17/24] 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 83125e4bb..ca8ebf430 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 58c45a3d9..5deb3cfb2 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 4539a8c5c99028f20886e238bbb43eaea4e07a2e Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 05:59:22 +0800 Subject: [PATCH 18/24] 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 d2ab43cb8..3032c6f8f 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 fee301d1b2e09ed1bbfc1e1ba53b259c49a4b2bc Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:09:30 +0800 Subject: [PATCH 19/24] 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 df8c9c99d..80cf2db9a 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 a74e7e155b7f8eeb5221295930df3b06935e71bd Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:10:26 +0800 Subject: [PATCH 20/24] 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 3032c6f8f..858870ec9 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 1dc1205b36a856d7f4eb2d84ae933b6c1ae4d1b6 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:45:21 +0800 Subject: [PATCH 21/24] 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 858870ec9..71642808d 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 2ff246c45..bbb843a3e 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 ca8ebf430..0386ac7fb 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 5deb3cfb2..831490b6e 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 100419ac67b68154a0f8c37aa8844d19d5739601 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 06:53:49 +0800 Subject: [PATCH 22/24] 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 d90dd4328..e8310c02d 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 9ee2f5b0d..df47302ff 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 3c634bbd97eef3559acfdcf64a5d0d5a5d96d294 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 08:01:20 +0800 Subject: [PATCH 23/24] 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 72e38c038..fb99f2c0e 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 55f44950b17ebef76defa408774f988cf7508423 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 17 Apr 2026 08:22:09 +0800 Subject: [PATCH 24/24] 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 e8310c02d..30fd52804 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 df47302ff..0c57c4a0b 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 {} };