From 08198cb1ca0a34de668dfe6c3107c0a439f380e9 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Mon, 11 Mar 2024 08:27:02 -0500 Subject: [PATCH 01/23] Make the library modular usable. --- build.jam | 32 ++++++++++++++++++++++++++++++++ build/Jamfile | 9 +++------ example/Jamfile | 17 ++++++++--------- test/Jamfile.jam | 6 +++--- 4 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 build.jam diff --git a/build.jam b/build.jam new file mode 100644 index 00000000..fd8d9da1 --- /dev/null +++ b/build.jam @@ -0,0 +1,32 @@ +# Copyright René Ferdinand Rivera Morell 2024 +# 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) + +import project ; + +project /boost/cobalt + : common-requirements + /boost/asio//boost_asio + /boost/circular_buffer//boost_circular_buffer + /boost/config//boost_config + /boost/container//boost_container + /boost/core//boost_core + /boost/intrusive//boost_intrusive + /boost/leaf//boost_leaf + /boost/mp11//boost_mp11 + /boost/preprocessor//boost_preprocessor + /boost/smart_ptr//boost_smart_ptr + /boost/system//boost_system + /boost/throw_exception//boost_throw_exception + /boost/variant2//boost_variant2 + include + ; + +explicit + [ alias boost_cobalt : build//boost_cobalt ] + [ alias all : boost_cobalt test example ] + ; + +call-if : boost-library cobalt + ; diff --git a/build/Jamfile b/build/Jamfile index 64695b91..156eb6cc 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -6,7 +6,7 @@ import os ; import feature ; -import ../../config/checks/config : requires ; +import config : requires ; project : requirements @@ -53,7 +53,7 @@ lib boost_cobalt [ requires cxx20_hdr_concepts ] - boost-container:/boost//container + boost-container:/boost/container//boost_container [ check-target-builds $(config-binding:D)//cpp_lib_memory_resource cpp_lib_memory_resource @@ -62,7 +62,7 @@ lib boost_cobalt ] : usage-requirements - boost-container:/boost//container + boost-container:/boost/container//boost_container shared:BOOST_COBALT_DYN_LINK=1 [ check-target-builds $(config-binding:D)//cpp_lib_memory_resource @@ -97,6 +97,3 @@ rule set-pmr-std ( props * ) return std ; } } - -boost-install boost_cobalt ; - diff --git a/example/Jamfile b/example/Jamfile index dbfa9768..ec019ebd 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -18,12 +18,11 @@ project : requirements clang-14:boost-container ; -exe channel : channel.cpp /boost//cobalt ; -exe delay : delay.cpp /boost//cobalt ; -exe delay_op : delay_op.cpp /boost//cobalt ; -exe echo_server : echo_server.cpp /boost//cobalt ; -exe outcome : outcome.cpp /boost//cobalt ; -exe thread : thread.cpp /boost//cobalt ; -exe thread_pool : thread_pool.cpp /boost//cobalt ; -# exe ticker : ticker.cpp /boost//json /boost//cobalt ; - +exe channel : channel.cpp /boost/cobalt//boost_cobalt ; +exe delay : delay.cpp /boost/cobalt//boost_cobalt ; +exe delay_op : delay_op.cpp /boost/cobalt//boost_cobalt ; +exe echo_server : echo_server.cpp /boost/cobalt//boost_cobalt ; +exe outcome : outcome.cpp /boost/cobalt//boost_cobalt /boost/outcome//boost_outcome ; +exe thread : thread.cpp /boost/cobalt//boost_cobalt ; +exe thread_pool : thread_pool.cpp /boost/cobalt//boost_cobalt ; +# exe ticker : ticker.cpp /boost/cobalt//boost_cobalt /boost/json//boost_json ; diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 450c8ea4..81748490 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -20,12 +20,12 @@ project : requirements import testing ; -lib test_impl : test_main.cpp /boost//cobalt /boost//unit_test_framework : +lib test_impl : test_main.cpp /boost/cobalt//boost_cobalt /boost/test//boost_unit_test_framework : static ; -run main.cpp /boost//cobalt ; -run main_compile.cpp /boost//cobalt util.cpp concepts.cpp ; +run main.cpp /boost/cobalt//boost_cobalt ; +run main_compile.cpp /boost/cobalt//boost_cobalt util.cpp concepts.cpp ; for local src in [ glob *.cpp : main.cpp main_compile.cpp test_main.cpp concepts.cpp util.cpp ] { From a9e843f13b51d05cc00cfd91f2fa3383f0c07325 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Fri, 29 Mar 2024 21:15:58 -0500 Subject: [PATCH 02/23] Switch to library requirements instead of source. As source puts extra source in install targets. --- build.jam | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/build.jam b/build.jam index fd8d9da1..1f4a10eb 100644 --- a/build.jam +++ b/build.jam @@ -7,19 +7,19 @@ import project ; project /boost/cobalt : common-requirements - /boost/asio//boost_asio - /boost/circular_buffer//boost_circular_buffer - /boost/config//boost_config - /boost/container//boost_container - /boost/core//boost_core - /boost/intrusive//boost_intrusive - /boost/leaf//boost_leaf - /boost/mp11//boost_mp11 - /boost/preprocessor//boost_preprocessor - /boost/smart_ptr//boost_smart_ptr - /boost/system//boost_system - /boost/throw_exception//boost_throw_exception - /boost/variant2//boost_variant2 + /boost/asio//boost_asio + /boost/circular_buffer//boost_circular_buffer + /boost/config//boost_config + /boost/container//boost_container + /boost/core//boost_core + /boost/intrusive//boost_intrusive + /boost/leaf//boost_leaf + /boost/mp11//boost_mp11 + /boost/preprocessor//boost_preprocessor + /boost/smart_ptr//boost_smart_ptr + /boost/system//boost_system + /boost/throw_exception//boost_throw_exception + /boost/variant2//boost_variant2 include ; From c0f0768eeba385f81ee6c43cbfd69c5ad0a16f89 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Sat, 20 Apr 2024 20:09:25 +0800 Subject: [PATCH 03/23] fix promise & generator operator= --- include/boost/cobalt/detail/generator.hpp | 43 +++++++++++++++++++---- include/boost/cobalt/detail/promise.hpp | 37 +++++++++++++++---- include/boost/cobalt/generator.hpp | 14 ++++++-- include/boost/cobalt/promise.hpp | 7 ++-- test/generator.cpp | 5 +++ test/promise.cpp | 7 ++-- 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/include/boost/cobalt/detail/generator.hpp b/include/boost/cobalt/detail/generator.hpp index 2c2760cb..4f793ec9 100644 --- a/include/boost/cobalt/detail/generator.hpp +++ b/include/boost/cobalt/detail/generator.hpp @@ -104,7 +104,7 @@ struct generator_receiver : generator_receiver_base { if (!lhs.done && !lhs.exception) { - reference = this; + *reference = this; lhs.exception = moved_from_exception(); } lhs.done = true; @@ -112,18 +112,47 @@ struct generator_receiver : generator_receiver_base ~generator_receiver() { - if (!done && reference == this) - reference = nullptr; + if (!done && *reference == this) + *reference = nullptr; } generator_receiver(generator_receiver * &reference, asio::cancellation_signal & cancel_signal) - : reference(reference), cancel_signal(cancel_signal) + : reference(&reference), cancel_signal(&cancel_signal) { reference = this; } - generator_receiver * &reference; - asio::cancellation_signal & cancel_signal; + + generator_receiver& operator=(generator_receiver && lhs) noexcept + { + if (*reference == this) + { + *reference = nullptr; + } + + generator_receiver_base::operator=(std::move(lhs)); + exception = std::move(lhs.exception); + done = lhs.done; + result = std::move(lhs.result); + result_buffer = std::move(lhs.result_buffer); + awaited_from = std::move(lhs.awaited_from); + yield_from = std::move(lhs.yield_from); + lazy = lhs.lazy; + reference = lhs.reference; + cancel_signal = lhs.cancel_signal; + + if (!lhs.done && !lhs.exception) + { + *reference = this; + lhs.exception = moved_from_exception(); + } + lhs.done = true; + + return *this; + } + + generator_receiver **reference; + asio::cancellation_signal * cancel_signal; using yield_awaitable = generator_yield_awaitable; @@ -185,7 +214,7 @@ struct generator_receiver : generator_receiver_base if constexpr (requires (Promise p) {p.get_cancellation_slot();}) if ((cl = h.promise().get_cancellation_slot()).is_connected()) - cl.emplace(self->cancel_signal); + cl.emplace(*self->cancel_signal); self->awaited_from.reset(h.address()); diff --git a/include/boost/cobalt/detail/promise.hpp b/include/boost/cobalt/detail/promise.hpp index e9dc78ed..68b2035c 100644 --- a/include/boost/cobalt/detail/promise.hpp +++ b/include/boost/cobalt/detail/promise.hpp @@ -120,22 +120,45 @@ struct promise_receiver : promise_value_holder { if (!done && !exception) { - reference = this; + *reference = this; lhs.exception = moved_from_exception(); } lhs.done = true; + } + + promise_receiver& operator=(promise_receiver && lhs) noexcept + { + if (*reference == this) + { + *reference = nullptr; + } + + promise_value_holder::operator=(std::move(lhs)); + exception = std::move(lhs.exception); + done = std::move(lhs.done); + awaited_from = std::move(lhs.awaited_from); + reference = std::move(lhs.reference); + cancel_signal = std::move(lhs.cancel_signal); + if (!done && !exception) + { + *reference = this; + lhs.exception = moved_from_exception(); + } + + return *this; } + ~promise_receiver() { - if (!done && reference == this) - reference = nullptr; + if (!done && *reference == this) + *reference = nullptr; } promise_receiver(promise_receiver * &reference, asio::cancellation_signal & cancel_signal) - : reference(reference), cancel_signal(cancel_signal) + : reference(&reference), cancel_signal(&cancel_signal) { reference = this; } @@ -176,7 +199,7 @@ struct promise_receiver : promise_value_holder if constexpr (requires (Promise p) {p.get_cancellation_slot();}) if ((cl = h.promise().get_cancellation_slot()).is_connected()) - cl.emplace(self->cancel_signal); + cl.emplace(*self->cancel_signal); self->awaited_from.reset(h.address()); return true; @@ -232,8 +255,8 @@ struct promise_receiver : promise_value_holder } }; - promise_receiver * &reference; - asio::cancellation_signal & cancel_signal; + promise_receiver **reference; + asio::cancellation_signal * cancel_signal; awaitable get_awaitable() {return awaitable{this};} diff --git a/include/boost/cobalt/generator.hpp b/include/boost/cobalt/generator.hpp index 2ef76d99..06ab1ebf 100644 --- a/include/boost/cobalt/generator.hpp +++ b/include/boost/cobalt/generator.hpp @@ -24,7 +24,7 @@ struct [[nodiscard]] generator // Movable generator(generator &&lhs) noexcept = default; - generator& operator=(generator &&) noexcept = default; + generator& operator=(generator &&) noexcept; // True until it co_returns & is co_awaited after <1> explicit operator bool() const; @@ -89,8 +89,8 @@ inline generator::operator bool() const template inline void generator::cancel(asio::cancellation_type ct) { - if (!receiver_.done && receiver_.reference == &receiver_) - receiver_.cancel_signal.emit(ct); + if (!receiver_.done && *receiver_.reference == &receiver_) + receiver_.cancel_signal->emit(ct); } template @@ -107,6 +107,14 @@ inline Yield generator::get() template inline generator::~generator() { cancel(); } +template +inline +generator& generator::operator=(generator && lhs) noexcept +{ + cancel(); + receiver_ = std::move(lhs.receiver_); + return *this; +} } diff --git a/include/boost/cobalt/promise.hpp b/include/boost/cobalt/promise.hpp index 0094f2dd..9107c174 100644 --- a/include/boost/cobalt/promise.hpp +++ b/include/boost/cobalt/promise.hpp @@ -87,10 +87,11 @@ template inline promise& promise::operator=(promise && lhs) noexcept { - if (attached_) + if (attached_) cancel(); receiver_ = std::move(lhs.receiver_); attached_ = std::exchange(lhs.attached_, false); + return *this; } template @@ -106,8 +107,8 @@ template inline void promise::cancel(asio::cancellation_type ct) { - if (!receiver_.done && receiver_.reference == &receiver_) - receiver_.cancel_signal.emit(ct); + if (!receiver_.done && *receiver_.reference == &receiver_) + receiver_.cancel_signal->emit(ct); } template diff --git a/test/generator.cpp b/test/generator.cpp index a11d99cd..2bafefc9 100644 --- a/test/generator.cpp +++ b/test/generator.cpp @@ -47,6 +47,11 @@ CO_TEST_CASE(generator_int) BOOST_CHECK(i == 11); + g = gen(); + BOOST_CHECK(g); + while (g) + co_await g; + co_return ; } diff --git a/test/promise.cpp b/test/promise.cpp index ff550e11..c1fe8746 100644 --- a/test/promise.cpp +++ b/test/promise.cpp @@ -146,7 +146,7 @@ struct promise_move_only { promise_move_only() = default; promise_move_only(promise_move_only &&) = default; - promise_move_only & operator=(promise_move_only &&) = delete; + promise_move_only & operator=(promise_move_only &&) = default; }; cobalt::promise pro_move_only_test() @@ -156,7 +156,10 @@ cobalt::promise pro_move_only_test() CO_TEST_CASE(move_only) { - co_await pro_move_only_test(); + auto p = pro_move_only_test(); + co_await p; + p = pro_move_only_test(); + co_await p; } BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file From 77d3d89c76aa2fff9c4a6e32c134a1596316b047 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 14 Mar 2024 09:02:16 +0800 Subject: [PATCH 04/23] added fno-exceptions support. --- include/boost/cobalt/detail/detached.hpp | 7 +++-- include/boost/cobalt/detail/gather.hpp | 10 ++++--- include/boost/cobalt/detail/join.hpp | 11 ++++---- include/boost/cobalt/detail/main.hpp | 2 ++ include/boost/cobalt/detail/race.hpp | 21 +++++++++------ include/boost/cobalt/detail/thread.hpp | 2 ++ include/boost/cobalt/detail/util.hpp | 6 +++-- include/boost/cobalt/error.hpp | 2 +- include/boost/cobalt/op.hpp | 6 +++-- include/boost/cobalt/result.hpp | 34 +++++++++++++++++------- include/boost/cobalt/with.hpp | 23 +++++++++++++++- src/thread.cpp | 6 +++-- 12 files changed, 92 insertions(+), 38 deletions(-) diff --git a/include/boost/cobalt/detail/detached.hpp b/include/boost/cobalt/detail/detached.hpp index 97c8b98c..63423508 100644 --- a/include/boost/cobalt/detail/detached.hpp +++ b/include/boost/cobalt/detail/detached.hpp @@ -74,10 +74,9 @@ struct detached_promise std::suspend_never final_suspend() noexcept {return {};} void return_void() {} - void unhandled_exception() - { - throw ; - } +#if !defined(BOOST_NO_EXCEPTIONS) + void unhandled_exception() { throw ; } +#endif }; } diff --git a/include/boost/cobalt/detail/gather.hpp b/include/boost/cobalt/detail/gather.hpp index c1d64078..171be8e1 100644 --- a/include/boost/cobalt/detail/gather.hpp +++ b/include/boost/cobalt/detail/gather.hpp @@ -93,7 +93,7 @@ struct gather_variadic_impl // GCC doesn't like member funs template static detail::fork await_impl(awaitable & this_) - try + BOOST_TRY { auto & aw = std::get(this_.aws); // check manually if we're ready @@ -124,10 +124,11 @@ struct gather_variadic_impl std::get(this_.result).template emplace<1u>(aw.await_resume()); } } - catch(...) + BOOST_CATCH(...) { std::get(this_.result).template emplace<2u>(std::current_exception()); } + BOOST_CATCH_END std::array impls { [](std::index_sequence) @@ -301,7 +302,7 @@ struct gather_ranged_impl } static detail::fork await_impl(awaitable & this_, std::size_t idx) - try + BOOST_TRY { auto & aw = *std::next(std::begin(this_.aws), idx); auto rd = aw.await_ready(); @@ -328,11 +329,12 @@ struct gather_ranged_impl this_.result[idx].template emplace<1u>(aw.await_resume()); } } - catch(...) + BOOST_CATCH(...) { this_.result[idx].template emplace<2u>(std::current_exception()); } + BOOST_CATCH_END detail::fork last_forked; std::size_t last_index = 0u; diff --git a/include/boost/cobalt/detail/join.hpp b/include/boost/cobalt/detail/join.hpp index eccbce48..221ab53e 100644 --- a/include/boost/cobalt/detail/join.hpp +++ b/include/boost/cobalt/detail/join.hpp @@ -113,11 +113,10 @@ struct join_variadic_impl }); } - // GCC doesn't like member funs template static detail::fork await_impl(awaitable & this_) - try + BOOST_TRY { auto & aw = std::get(this_.aws); // check manually if we're ready @@ -152,12 +151,13 @@ struct join_variadic_impl } } - catch(...) + BOOST_CATCH(...) { if (!this_.error) this_.error = std::current_exception(); this_.cancel_all(); } + BOOST_CATCH_END std::array impls { [](std::index_sequence) @@ -393,7 +393,7 @@ struct join_ranged_impl static detail::fork await_impl(awaitable & this_, std::size_t idx) - try + BOOST_TRY { auto & aw = *std::next(std::begin(this_.aws), idx); auto rd = aw.await_ready(); @@ -415,12 +415,13 @@ struct join_ranged_impl this_.result[idx].emplace(aw.await_resume()); } } - catch(...) + BOOST_CATCH(...) { if (!this_.error) this_.error = std::current_exception(); this_.cancel_all(); } + BOOST_CATCH_END detail::fork last_forked; std::size_t last_index = 0u; diff --git a/include/boost/cobalt/detail/main.hpp b/include/boost/cobalt/detail/main.hpp index 6481e293..33f60676 100644 --- a/include/boost/cobalt/detail/main.hpp +++ b/include/boost/cobalt/detail/main.hpp @@ -66,7 +66,9 @@ struct main_promise : signal_helper, BOOST_COBALT_DECL auto final_suspend() noexcept -> std::suspend_never; +#if !defined(BOOST_NO_EXCEPTIONS) void unhandled_exception() { throw ; } +#endif void return_value(int res = 0) { if (result) diff --git a/include/boost/cobalt/detail/race.hpp b/include/boost/cobalt/detail/race.hpp index 72eb6eb9..90f6b824 100644 --- a/include/boost/cobalt/detail/race.hpp +++ b/include/boost/cobalt/detail/race.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -149,14 +150,15 @@ struct race_variadic_impl template void assign_error(system::result & res) - try + BOOST_TRY { std::move(res).value(loc); } - catch(...) + BOOST_CATCH(...) { error = std::current_exception(); } + BOOST_CATCH_END template void assign_error(system::result & res) @@ -166,7 +168,7 @@ struct race_variadic_impl template static detail::fork await_impl(awaitable & this_) - try + BOOST_TRY { using traits = race_traits, Idx>>; @@ -273,7 +275,7 @@ struct race_variadic_impl this_.cancel_all(); this_.working[Idx] = nullptr; } - catch(...) + BOOST_CATCH(...) { if (!this_.has_result()) this_.index = Idx; @@ -281,6 +283,7 @@ struct race_variadic_impl this_.error = std::current_exception(); this_.working[Idx] = nullptr; } + BOOST_CATCH_END std::array impls { [](std::index_sequence) @@ -488,14 +491,15 @@ struct race_ranged_impl template void assign_error(system::result & res) - try + BOOST_TRY { std::move(res).value(loc); } - catch(...) + BOOST_CATCH(...) { error = std::current_exception(); } + BOOST_CATCH_END template void assign_error(system::result & res) @@ -504,7 +508,7 @@ struct race_ranged_impl } static detail::fork await_impl(awaitable & this_, std::size_t idx) - try + BOOST_TRY { typename traits::actual_awaitable aw_{ get_awaitable_type( @@ -591,7 +595,7 @@ struct race_ranged_impl if constexpr (traits::interruptible) this_.working[idx] = nullptr; } - catch(...) + BOOST_CATCH(...) { if (!this_.has_result()) this_.index = idx; @@ -600,6 +604,7 @@ struct race_ranged_impl if constexpr (traits::interruptible) this_.working[idx] = nullptr; } + BOOST_CATCH_END detail::fork last_forked; diff --git a/include/boost/cobalt/detail/thread.hpp b/include/boost/cobalt/detail/thread.hpp index 1631e873..bd73053b 100644 --- a/include/boost/cobalt/detail/thread.hpp +++ b/include/boost/cobalt/detail/thread.hpp @@ -80,7 +80,9 @@ struct thread_promise : signal_helper_2, return {}; } +#if !defined(BOOST_NO_EXCEPTIONS) void unhandled_exception() { throw; } +#endif void return_void() { } using executor_type = typename cobalt::executor; diff --git a/include/boost/cobalt/detail/util.hpp b/include/boost/cobalt/detail/util.hpp index fac0d284..0fe2cf2d 100644 --- a/include/boost/cobalt/detail/util.hpp +++ b/include/boost/cobalt/detail/util.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -104,7 +105,7 @@ template auto get_resume_result(Awaitable & aw) -> system::result { using type = decltype(aw.await_resume()); - try + BOOST_TRY { if constexpr (std::is_void_v) { @@ -114,10 +115,11 @@ auto get_resume_result(Awaitable & aw) -> system::result #include #include +#include namespace boost::cobalt { @@ -54,7 +55,7 @@ struct op #endif ) noexcept { - try + BOOST_TRY { completed_immediately = detail::completed_immediately_t::initiating; @@ -67,11 +68,12 @@ struct op completed_immediately = detail::completed_immediately_t::no; return completed_immediately != detail::completed_immediately_t::yes; } - catch(...) + BOOST_CATCH(...) { init_ep = std::current_exception(); return false; } + BOOST_CATCH_END } auto await_resume(const boost::source_location & loc = BOOST_CURRENT_LOCATION) diff --git a/include/boost/cobalt/result.hpp b/include/boost/cobalt/result.hpp index a1300440..5aa15ac8 100644 --- a/include/boost/cobalt/result.hpp +++ b/include/boost/cobalt/result.hpp @@ -7,6 +7,7 @@ #include +#include #include namespace boost::cobalt @@ -101,27 +102,29 @@ struct as_result_t if constexpr (std::is_void_v) { using res_t = system::result; - try + BOOST_TRY { aw_.await_resume(); return res_t{system::in_place_value}; } - catch (...) + BOOST_CATCH (...) { return res_t{system::in_place_error, std::current_exception()}; } + BOOST_CATCH_END } else { using res_t = system::result; - try + BOOST_TRY { return res_t{system::in_place_value, aw_.await_resume()}; } - catch (...) + BOOST_CATCH (...) { return res_t{system::in_place_error, std::current_exception()}; } + BOOST_CATCH_END } } } @@ -188,33 +191,46 @@ struct as_tuple_t auto await_resume() { + using type = decltype(aw_.await_resume()); if constexpr (requires {aw_.await_resume(as_tuple_tag{});}) return aw_.await_resume(as_tuple_tag{}); + else if (noexcept(aw_.await_resume())) + { + if constexpr (std::is_void_v) + { + aw_.await_resume(); + return std::make_tuple(); + } + else + return std::make_tuple(aw_.await_resume()); + + } else { - using type = decltype(aw_.await_resume()); if constexpr (std::is_void_v) { - try + BOOST_TRY { aw_.await_resume(); return std::make_tuple(std::exception_ptr()); } - catch (...) + BOOST_CATCH (...) { return make_tuple_(std::current_exception()); } + BOOST_CATCH_END } else { - try + BOOST_TRY { return make_tuple_(std::exception_ptr(), aw_.await_resume()); } - catch (...) + BOOST_CATCH (...) { return make_tuple_(std::current_exception(), type()); } + BOOST_CATCH_END } } } diff --git a/include/boost/cobalt/with.hpp b/include/boost/cobalt/with.hpp index ed8a4f6b..bdfb21fa 100644 --- a/include/boost/cobalt/with.hpp +++ b/include/boost/cobalt/with.hpp @@ -39,7 +39,12 @@ template }) auto with(Arg arg, Func func, Teardown teardown) -> detail::with_impl { + std::exception_ptr e; +#if defined(BOOST_NO_EXCEPTIONS) + co_await std::move(func)(arg); + co_await std::move(teardown)(arg, e); +#else try { co_await std::move(func)(arg); @@ -60,6 +65,7 @@ auto with(Arg arg, Func func, Teardown teardown) -> detail::with_impl } if (e) std::rethrow_exception(e); +#endif } @@ -76,6 +82,10 @@ template auto with(Arg arg, Func func, Teardown teardown) -> detail::with_impl { std::exception_ptr e; +#if defined(BOOST_NO_EXCEPTIONS) + std::move(func)(arg); + co_await std::move(teardown)(arg, e); +#else try { std::move(func)(arg); @@ -96,6 +106,7 @@ auto with(Arg arg, Func func, Teardown teardown) -> detail::with_impl } if (e) std::rethrow_exception(e); +#endif } @@ -112,6 +123,10 @@ auto with(Arg arg, Func func, Teardown teardown) std::exception_ptr e; std::optional> res; +#if defined(BOOST_NO_EXCEPTIONS) + res = co_await std::move(func)(arg); + co_await std::move(teardown)(std::move(arg), e); +#else try { res = co_await std::move(func)(arg); @@ -132,6 +147,7 @@ auto with(Arg arg, Func func, Teardown teardown) } if (e) std::rethrow_exception(e); +#endif co_return std::move(res); } @@ -150,6 +166,11 @@ auto with(Arg arg, Func func, Teardown teardown) -> detail::with_impl res; + +#if defined(BOOST_NO_EXCEPTIONS) + res = std::move(func)(arg); + co_await std::move(teardown)(arg, e); +#else try { res = std::move(func)(arg); @@ -170,7 +191,7 @@ auto with(Arg arg, Func func, Teardown teardown) -> detail::with_impl #include +#include namespace boost::cobalt { @@ -51,14 +52,15 @@ void run_thread( std::exception_ptr ep; - try + BOOST_TRY { st->ctx.run(); } - catch(...) + BOOST_CATCH(...) { ep = std::current_exception(); } + BOOST_CATCH_END st->done = true; st->signal.slot().clear(); From 3071c0f64753bce6cf7fa343a289c1064f9cbe91 Mon Sep 17 00:00:00 2001 From: Klemens Date: Sun, 10 Mar 2024 14:21:41 +0800 Subject: [PATCH 05/23] added experimental context support. --- .drone.star | 2 + include/boost/cobalt/detail/detached.hpp | 2 +- include/boost/cobalt/detail/generator.hpp | 2 +- include/boost/cobalt/detail/main.hpp | 2 +- include/boost/cobalt/detail/promise.hpp | 2 +- include/boost/cobalt/detail/task.hpp | 2 +- include/boost/cobalt/detail/with.hpp | 2 +- include/boost/cobalt/experimental/context.hpp | 367 ++++++++++++++++++ test/CMakeLists.txt | 7 +- test/Jamfile.jam | 1 + test/experimental/context.cpp | 86 ++++ 11 files changed, 468 insertions(+), 7 deletions(-) create mode 100644 include/boost/cobalt/experimental/context.hpp create mode 100644 test/experimental/context.cpp diff --git a/.drone.star b/.drone.star index d507de30..251512a4 100644 --- a/.drone.star +++ b/.drone.star @@ -13,11 +13,13 @@ deps = [ 'libs/assert', 'libs/beast', 'libs/bind', + 'libs/callable_traits', 'libs/chrono', 'libs/circular_buffer', 'libs/concept_check', 'libs/config', 'libs/container', + 'libs/context', 'libs/core', 'libs/date_time', 'libs/detail', diff --git a/include/boost/cobalt/detail/detached.hpp b/include/boost/cobalt/detail/detached.hpp index 63423508..2ab5e276 100644 --- a/include/boost/cobalt/detail/detached.hpp +++ b/include/boost/cobalt/detail/detached.hpp @@ -70,7 +70,7 @@ struct detached_promise { } - std::suspend_never initial_suspend() {return {};} + std::suspend_never initial_suspend() noexcept {return {};} std::suspend_never final_suspend() noexcept {return {};} void return_void() {} diff --git a/include/boost/cobalt/detail/generator.hpp b/include/boost/cobalt/detail/generator.hpp index 4f793ec9..ff34c819 100644 --- a/include/boost/cobalt/detail/generator.hpp +++ b/include/boost/cobalt/detail/generator.hpp @@ -374,7 +374,7 @@ struct generator_promise this->reset_cancellation_source(signal.slot()); } - std::suspend_never initial_suspend() {return {};} + std::suspend_never initial_suspend() noexcept {return {};} struct final_awaitable { diff --git a/include/boost/cobalt/detail/main.hpp b/include/boost/cobalt/detail/main.hpp index 33f60676..b8b5aa9c 100644 --- a/include/boost/cobalt/detail/main.hpp +++ b/include/boost/cobalt/detail/main.hpp @@ -61,7 +61,7 @@ struct main_promise : signal_helper, return my_resource->deallocate(raw, size); } #endif - std::suspend_always initial_suspend() {return {};} + std::suspend_always initial_suspend() noexcept {return {};} BOOST_COBALT_DECL auto final_suspend() noexcept -> std::suspend_never; diff --git a/include/boost/cobalt/detail/promise.hpp b/include/boost/cobalt/detail/promise.hpp index 68b2035c..9b45aafb 100644 --- a/include/boost/cobalt/detail/promise.hpp +++ b/include/boost/cobalt/detail/promise.hpp @@ -340,7 +340,7 @@ struct cobalt_promise this->reset_cancellation_source(signal.slot()); } - std::suspend_never initial_suspend() {return {};} + std::suspend_never initial_suspend() noexcept {return {};} auto final_suspend() noexcept { return final_awaitable{this}; diff --git a/include/boost/cobalt/detail/task.hpp b/include/boost/cobalt/detail/task.hpp index 49a33334..8aff2a58 100644 --- a/include/boost/cobalt/detail/task.hpp +++ b/include/boost/cobalt/detail/task.hpp @@ -328,7 +328,7 @@ struct task_promise } }; - auto initial_suspend() + auto initial_suspend() noexcept { return initial_awaitable{this}; diff --git a/include/boost/cobalt/detail/with.hpp b/include/boost/cobalt/detail/with.hpp index cc47d27c..0a0fbf05 100644 --- a/include/boost/cobalt/detail/with.hpp +++ b/include/boost/cobalt/detail/with.hpp @@ -79,7 +79,7 @@ struct with_impl::promise_type e = std::current_exception(); } - std::suspend_always initial_suspend() {return {};} + std::suspend_always initial_suspend() noexcept {return {};} struct final_awaitable { diff --git a/include/boost/cobalt/experimental/context.hpp b/include/boost/cobalt/experimental/context.hpp new file mode 100644 index 00000000..608d4dcf --- /dev/null +++ b/include/boost/cobalt/experimental/context.hpp @@ -0,0 +1,367 @@ +// Copyright (c) 2024 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_COBALT_CONTEXT_HPP +#define BOOST_COBALT_CONTEXT_HPP + +#include +#include +#include + +#include +#include +#include +#include +// this is all UB according to the standard. BUT it shouldn't be! + +namespace boost::cobalt::experimental +{ + +template +struct context; + +namespace detail +{ + +template +struct fiber_frame +{ + void (*resume_) (fiber_frame *) = +[](fiber_frame * ff) { ff->resume();}; + void (*destroy_)(fiber_frame *) = +[](fiber_frame * ff) { ff->destroy();}; + + Promise promise; + + boost::context::fiber caller, callee; + + void (*after_resume)(fiber_frame *, void *) = nullptr; + void * after_resume_p; + + template + requires std::constructible_from + fiber_frame(Args && ... args) : promise(args...) {} + + template + requires (!std::constructible_from && std::is_default_constructible_v) + fiber_frame(Args && ...) {} + + void resume() + { + callee = std::move(callee).resume(); + if (auto af = std::exchange(after_resume, nullptr)) + af(this, after_resume_p); + } + void destroy() + { + auto c = std::exchange(callee, {}); + this->~fiber_frame(); + } + + template + auto do_await(Awaitable aw) + { + if (!aw.await_ready()) + { + after_resume_p = & aw; + after_resume = + +[](fiber_frame * this_, void * p) + { + auto aw_ = static_cast(p); + auto h = std::coroutine_handle::from_address(this_) ; + if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as;}) + aw_->await_suspend(h); + else if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as;}) + { + if (!aw_->await_suspend(h)) + h.resume(); + } + else if constexpr (requires {{aw_->await_suspend(h)} -> std::convertible_to>;}) + aw_->await_suspend(h).resume(); + else + static_assert(std::is_void_v, "Invalid return from await_suspend()"); + }; + caller = std::move(caller).resume(); + } + return aw.await_resume(); + } + + template + context get_context() + { + return context{this}; + } + +}; + + +template +struct stack_allocator : boost::context::fixedsize_stack {}; + +template +requires requires {Promise::operator new(std::size_t{});} +struct stack_allocator +{ + boost::context::stack_context allocate() + { + const auto size = Traits::default_size(); + const auto p = Promise::operator new(size); + + boost::context::stack_context sctx; + sctx.size = size; + sctx.sp = static_cast< char * >( p) + sctx.size; + return sctx; + } + + void deallocate( boost::context::stack_context & sctx) noexcept + { + void * vp = static_cast< char * >( sctx.sp) - sctx.size; + Promise::operator delete(vp, sctx.size); + } +}; + +template + requires requires {Promise::operator new(std::size_t{}, std::decay_t()...);} +struct stack_allocator +{ + std::tuple args; + + boost::context::stack_context allocate() + { + const auto size = Traits::default_size(); + const auto p = std::apply( + [size](auto & ... args_) + { + return Promise::operator new(size, args_...); + }, args); + + boost::context::stack_context sctx; + sctx.size = size; + sctx.sp = static_cast< char * >( p) + sctx.size; + return sctx; + } + + void deallocate( boost::context::stack_context & sctx) noexcept + { + void * vp = static_cast< char * >( sctx.sp) - sctx.size; + Promise::operator delete(vp, sctx.size); + } +}; + + +struct await_transform_base +{ + struct dummy {}; + void await_transform(dummy); +}; + +template +struct await_transform_impl : await_transform_base, T +{ +}; + +template +concept has_await_transform = ! requires (await_transform_impl & p) {p.await_transform(await_transform_base::dummy{});}; + +} + +template +struct context +{ + using return_type = Return; + using promise_type = typename std::coroutine_traits::promise_type; + + promise_type & promise() {return frame_->promise;} + const promise_type & promise() const {return frame_->promise;} + template + requires std::same_as::promise_type> + constexpr operator context() const + { + return {frame_}; + } + + template + requires (detail::has_await_transform && + requires (promise_type & pro, Awaitable && aw) + { + {pro.await_transform(std::forward(aw))} -> awaitable_type; + }) + auto await(Awaitable && aw) + { + return frame_->do_await(frame_->promise.await_transform(std::forward(aw))); + } + + template + requires (detail::has_await_transform && + requires (promise_type & pro, Awaitable && aw) + { + {pro.await_transform(std::forward(aw).operator co_await())} -> awaitable_type; + }) + auto await(Awaitable && aw) + { + return frame_->do_await(frame_->promise.await_transform(std::forward(aw)).operator co_await()); + } + template + requires (detail::has_await_transform && + requires (promise_type & pro, Awaitable && aw) + { + {operator co_await(pro.await_transform(std::forward(aw)))} -> awaitable_type; + }) + auto await(Awaitable && aw) + { + return frame_->do_await(operator co_await(frame_->promise.await_transform(std::forward(aw)))); + } + template Awaitable> + requires (!detail::has_await_transform ) + auto await(Awaitable && aw) + { + return frame_->do_await(std::forward(aw)); + } + + template + requires (!detail::has_await_transform + && requires (Awaitable && aw) {{operator co_await(std::forward(aw))} -> awaitable_type;}) + auto await(Awaitable && aw) + { + return frame_->do_await(operator co_await(std::forward(aw))); + } + + template + requires (!detail::has_await_transform + && requires (Awaitable && aw) {{std::forward(aw).operator co_await()} -> awaitable_type;}) + auto await(Awaitable && aw) + { + return frame_->do_await(std::forward(aw).operator co_await()); + } + + template + requires requires (promise_type & pro, Yield && value) {{pro.yield_value(std::forward(value))} -> awaitable_type;} + auto yield(Yield && value) + { + frame_->do_await(frame_->promise.yield_value(std::forward(value))); + } + + private: + + context(detail::fiber_frame * frame) : frame_(frame) {} + template + friend struct context; + + //template + friend struct detail::fiber_frame; + + detail::fiber_frame * frame_; +}; + +template, Args...> Func, typename StackAlloc> +auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args) +{ + auto sctx_ = salloc.allocate(); + + using promise_type = typename std::coroutine_traits::promise_type; + void * p = static_cast(sctx_.sp) - sizeof(detail::fiber_frame); + auto sz = sctx_.size - sizeof(detail::fiber_frame); + + if (auto diff = reinterpret_cast(p) % alignof(detail::fiber_frame); diff != 0u) + { + p = static_cast(p) - diff; + sz -= diff; + } + + boost::context::preallocated psc{p, sz, sctx_}; + auto f = new (p) detail::fiber_frame(args...); + + auto res = f->promise.get_return_object(); + + constexpr auto is_always_lazy = + requires (promise_type & pro) {{pro.initial_suspend()} -> std::same_as;} + && noexcept(f->promise.initial_suspend()); + + struct invoker + { + detail::fiber_frame * frame; + mutable Func func; + mutable std::tuple args; + + invoker(detail::fiber_frame * frame, Func && func, Args && ... args) + : frame(frame), func(std::forward(func)), args(std::forward(args)...) + { + } + + boost::context::fiber operator()(boost::context::fiber && f) const + { + auto & promise = frame->promise; + frame->caller = std::move(f); + + try + { + if (!is_always_lazy) + frame->do_await(promise.initial_suspend()); + + std::apply( + [&](auto && ... args_) + { + auto ctx = frame->template get_context(); + using return_type = decltype(std::forward(func)(ctx, std::forward(args_)...)); + + if constexpr (std::is_void_v) + { + std::forward(func)(ctx, std::forward(args_)...); + frame->promise.return_void(); + } + else + frame->promise.return_value(std::forward(func)(ctx, std::forward(args_)...)); + + + }, + std::move(args)); + } + catch (boost::context::detail::forced_unwind &) { throw; } + catch (...) {promise.unhandled_exception();} + + static_assert(noexcept(promise.final_suspend())); + frame->do_await(promise.final_suspend()); + return std::move(frame->caller); + } + + }; + + f->callee = boost::context::fiber{ + std::allocator_arg, psc, std::forward(salloc), + invoker(f, std::forward(func), std::forward(args)...)}; + + if constexpr (is_always_lazy) + f->promise.initial_suspend(); + else + f->resume(); + + return res; +} + +template, Args...> Func> +auto make_context(Func && func, Args && ... args) +{ + return make_context(std::forward(func), std::allocator_arg, + boost::context::fixedsize_stack(), std::forward(args)...); +} + + +template +auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args) +{ + return make_context>::return_type>( + std::forward(func), std::allocator_arg, std::forward(salloc), std::forward(args)... + ); +} + + +template +auto make_context(Func && func, Args && ... args) +{ + return make_context>::return_type>( + std::forward(func), std::forward(args)... + ); +} + +} + +#endif //BOOST_COBALT_CONTEXT_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 032ff54b..d03ea243 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,4 +21,9 @@ add_test(NAME boost_cobalt_main COMMAND boost_cobalt_main) add_test(NAME boost_cobalt_basic_tests COMMAND boost_cobalt_basic_tests) target_compile_features(boost_cobalt PUBLIC cxx_std_20) -add_dependencies(tests boost_cobalt_main boost_cobalt_basic_tests boost_cobalt_static_tests) + +add_executable(boost_cobalt_experimental test_main.cpp experimental/context.cpp) +target_link_libraries(boost_cobalt_experimental Boost::cobalt Boost::unit_test_framework Boost::context) +add_test(NAME boost_cobalt_experimental COMMAND boost_cobalt_experimental) + +add_dependencies(tests boost_cobalt_main boost_cobalt_basic_tests boost_cobalt_static_tests boost_cobalt_experimental) diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 450c8ea4..696f22b7 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -32,3 +32,4 @@ for local src in [ glob *.cpp : main.cpp main_compile.cpp test_main.cpp concepts run $(src) test_impl ; } +run experimental/context.cpp test_impl //boost/context ; \ No newline at end of file diff --git a/test/experimental/context.cpp b/test/experimental/context.cpp new file mode 100644 index 00000000..1c73e564 --- /dev/null +++ b/test/experimental/context.cpp @@ -0,0 +1,86 @@ +// Copyright (c) 2024 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +#include "../test.hpp" + +using namespace boost::cobalt; + +BOOST_AUTO_TEST_SUITE(fiber); + +BOOST_AUTO_TEST_CASE(basics) +{ + boost::cobalt::experimental::detail::fiber_frame ff; + + auto hh = std::coroutine_handle::from_address(&ff); + BOOST_CHECK(!hh.done()); + ff.resume_ = nullptr; + BOOST_CHECK(hh.done()); + + BOOST_CHECK(&ff.promise == &hh.promise()); + BOOST_CHECK(std::coroutine_handle::from_promise(ff.promise).address() == &ff); +} + +int stackful_task(experimental::context> c, int init) +{ + static_assert(experimental::detail::has_await_transform); + BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); + + bool done = false; + boost::asio::post(c.promise().get_executor(), + [&]{done = true;}); + + BOOST_CHECK(!done); + c.await(boost::asio::post(use_op)); + BOOST_CHECK(done); + return init * 2; +} + +CO_TEST_CASE(task_) +{ + task t = experimental::make_context(&stackful_task, 12); + + BOOST_CHECK(24 == co_await t); +} + +void stackful_task_void(experimental::context> c) +{ + static_assert(experimental::detail::has_await_transform); + BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); +} + +CO_TEST_CASE(task_void) +{ + co_await experimental::make_context(&stackful_task_void); +} + +int stackful_generator(experimental::context> c) +{ + static_assert(experimental::detail::has_await_transform); + BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); + + c.yield(1); + c.yield(2); + c.yield(3); + c.yield(4); + + return 5; +} + +CO_TEST_CASE(generator_) +{ + generator g = experimental::make_context(&stackful_generator); + BOOST_CHECK(1 == co_await g); + BOOST_CHECK(2 == co_await g); + BOOST_CHECK(3 == co_await g); + BOOST_CHECK(4 == co_await g); + BOOST_CHECK(5 == co_await g); +} + +BOOST_AUTO_TEST_SUITE_END(); From c41660ea72f0ce2260d8e3d392355ac1e7c49230 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Tue, 12 Mar 2024 21:13:29 +0800 Subject: [PATCH 06/23] support for asio::yield_context --- .github/workflows/ci.yml | 20 ++- CMakeLists.txt | 1 + doc/index.adoc | 3 + doc/reference/experimental/context.adoc | 69 +++++++++ include/boost/cobalt/detail/fork.hpp | 4 +- include/boost/cobalt/experimental/context.hpp | 121 +++++++++------- include/boost/cobalt/experimental/frame.hpp | 35 +++++ .../cobalt/experimental/yield_context.hpp | 106 ++++++++++++++ test/CMakeLists.txt | 2 +- test/experimental/context.cpp | 4 +- test/experimental/yield_context.cpp | 131 ++++++++++++++++++ 11 files changed, 439 insertions(+), 57 deletions(-) create mode 100644 doc/reference/experimental/context.adoc create mode 100644 include/boost/cobalt/experimental/frame.hpp create mode 100644 include/boost/cobalt/experimental/yield_context.hpp create mode 100644 test/experimental/yield_context.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df243858..9d3f44fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,8 +97,10 @@ jobs: cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY git submodule update --init tools/boostdep python3 tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY + git submodule update --init libs/test - # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio @@ -165,6 +167,8 @@ jobs: python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% git submodule update --init libs/test # temporary workaround, + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio cmd /c bootstrap @@ -221,6 +225,8 @@ jobs: python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY git submodule update --init libs/test # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio @@ -277,6 +283,8 @@ jobs: python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY git submodule update --init libs/test # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio @@ -350,6 +358,8 @@ jobs: python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY git submodule update --init libs/test # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio @@ -403,6 +413,8 @@ jobs: python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% git submodule update --init libs/test # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio @@ -458,6 +470,8 @@ jobs: python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% git submodule update --init libs/test # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio @@ -531,8 +545,10 @@ jobs: xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\ git submodule update --init tools/boostdep python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% + git submodule update --init libs/test - # Temporary workaround + git submodule update --init libs/callable_traits + git submodule update --init libs/context git submodule update --init libs/chrono git submodule update --init libs/ratio diff --git a/CMakeLists.txt b/CMakeLists.txt index 35fafcea..776cff46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ if (NOT BOOST_COBALT_IS_ROOT) target_link_libraries(boost_cobalt PUBLIC Boost::asio + Boost::callable_traits Boost::circular_buffer Boost::config Boost::container diff --git a/doc/index.adoc b/doc/index.adoc index 34519d4b..0b12613e 100644 --- a/doc/index.adoc +++ b/doc/index.adoc @@ -78,6 +78,9 @@ include::reference/error.adoc[] include::reference/config.adoc[] include::reference/leaf.adoc[] +include::reference/experimental/context.adoc[] + + = In-Depth include::background/custom_executors.adoc[] diff --git a/doc/reference/experimental/context.adoc b/doc/reference/experimental/context.adoc new file mode 100644 index 00000000..4f59c2e4 --- /dev/null +++ b/doc/reference/experimental/context.adoc @@ -0,0 +1,69 @@ +[#context] +== cobalt/experimental/context.hpp + +WARNING: This is (most likely) undefined behaviour, since the violates a precondition in the standard. A paper to address this can be found here (https://isocpp.org/files/papers/P3203R0.html). + +This header provides `experimental` support for using `boost.fiber` based stackful coroutines +as if they were C++20 coroutines. That is, they can use `awaitables` by being able to be put into a `coroutine_handle`. +Likewise the implementation uses a C++20 coroutine promise and runs is as if it was a C++20 coroutine. + +[source,cpp] +---- +// +void delay(experimental::context> h, std::chrono::milliseconds ms) +{ + asio::steady_timer tim{co_await cobalt::this_coro::executor, ms}; + h.await(tim.async_wait(cobalt::use_op)); // instead of co_await. +} + +cobalt::main co_main(int argc, char *argv[]) +{ + cobalt::promise dl = cobalt::experimental::make_context(&delay, 50); + co_await dl; + co_return 0; +} +---- + +=== Reference + +[source,cpp] +---- +// The internal coroutine context. +/// Args are the function arguments after the handle. +template +struct context +{ + // Get a handle to the promise + promise_type & promise(); + const promise_type & promise() const; + + // Convert it to any context if the underlying promise is the same + template + constexpr operator context() const; + + // Await something. Uses await_transform automatically. + template + auto await(Awaitable && aw); + // Yield a value, if supported by the promise. + template + auto yield(Yield && value); +}; + + +// Create a fiber with a custom stack allocator (see boost.fiber for details) and explicit result (e.g. `promise`) +template, Args...> Func, typename StackAlloc> +auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args); + +// Create a fiber with the default allocator and explicit result (e.g. `promise`) +template, Args...> Func> +auto make_context(Func && func, Args && ... args); + +// Create a fiber with a custom stack allocator and implicit result (deduced from the first argument to func). +template +auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args); + +// Create a fiber with the default stack allocator and implicit result (deduced from the first argument to func). +template +auto make_context(Func && func, Args && ... args); +---- + diff --git a/include/boost/cobalt/detail/fork.hpp b/include/boost/cobalt/detail/fork.hpp index d630e9d4..230ee049 100644 --- a/include/boost/cobalt/detail/fork.hpp +++ b/include/boost/cobalt/detail/fork.hpp @@ -108,9 +108,7 @@ struct fork return st.resource.allocate(size); } - template - void operator delete(void * raw, const std::size_t size, Rest && ...) noexcept; - void operator delete(void *, const std::size_t) noexcept {} + void operator delete(void *) noexcept {} template promise_type(shared_state & st, Rest & ...) diff --git a/include/boost/cobalt/experimental/context.hpp b/include/boost/cobalt/experimental/context.hpp index 608d4dcf..5ae6aa4b 100644 --- a/include/boost/cobalt/experimental/context.hpp +++ b/include/boost/cobalt/experimental/context.hpp @@ -2,18 +2,18 @@ // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_COBALT_CONTEXT_HPP -#define BOOST_COBALT_CONTEXT_HPP +#ifndef BOOST_COBALT_EXPERIMENTAL_CONTEXT_HPP +#define BOOST_COBALT_EXPERIMENTAL_CONTEXT_HPP #include #include #include #include +#include #include #include #include -// this is all UB according to the standard. BUT it shouldn't be! namespace boost::cobalt::experimental { @@ -25,25 +25,20 @@ namespace detail { template -struct fiber_frame +struct context_frame : frame, Promise> { - void (*resume_) (fiber_frame *) = +[](fiber_frame * ff) { ff->resume();}; - void (*destroy_)(fiber_frame *) = +[](fiber_frame * ff) { ff->destroy();}; - - Promise promise; - boost::context::fiber caller, callee; - void (*after_resume)(fiber_frame *, void *) = nullptr; + void (*after_resume)(context_frame *, void *) = nullptr; void * after_resume_p; template requires std::constructible_from - fiber_frame(Args && ... args) : promise(args...) {} + context_frame(Args && ... args) : frame(args...) {} template requires (!std::constructible_from && std::is_default_constructible_v) - fiber_frame(Args && ...) {} + context_frame(Args && ...) {} void resume() { @@ -54,32 +49,53 @@ struct fiber_frame void destroy() { auto c = std::exchange(callee, {}); - this->~fiber_frame(); + this->~context_frame(); + } + + template + auto do_resume(void * ) + { + return +[](context_frame * this_, void * p) + { + auto aw_ = static_cast(p); + auto h = std::coroutine_handle::from_address(this_) ; + aw_->await_suspend(h); + }; } + template + auto do_resume(bool * ) + { + return +[](context_frame * this_, void * p) + { + auto aw_ = static_cast(p); + auto h = std::coroutine_handle::from_address(this_) ; + if (!aw_->await_suspend(h)) + h.resume(); + }; + } + + template + auto do_resume(std::coroutine_handle * ) + { + return +[](context_frame * this_, void * p) + { + auto aw_ = static_cast(p); + auto h = std::coroutine_handle::from_address(this_) ; + aw_->await_suspend(h).resume(); + }; + } + + template auto do_await(Awaitable aw) { if (!aw.await_ready()) { after_resume_p = & aw; - after_resume = - +[](fiber_frame * this_, void * p) - { - auto aw_ = static_cast(p); - auto h = std::coroutine_handle::from_address(this_) ; - if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as;}) - aw_->await_suspend(h); - else if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as;}) - { - if (!aw_->await_suspend(h)) - h.resume(); - } - else if constexpr (requires {{aw_->await_suspend(h)} -> std::convertible_to>;}) - aw_->await_suspend(h).resume(); - else - static_assert(std::is_void_v, "Invalid return from await_suspend()"); - }; + after_resume = do_resume( + static_cast>()))*>(nullptr) + ); caller = std::move(caller).resume(); } return aw.await_resume(); @@ -162,6 +178,20 @@ struct await_transform_impl : await_transform_base, T template concept has_await_transform = ! requires (await_transform_impl & p) {p.await_transform(await_transform_base::dummy{});}; +template +void do_return(std::true_type /* is_void */, Promise& promise, Context ctx, Func && func, Args && ... args) +{ + std::forward(func)(ctx, std::forward(args)...); + promise.return_void(); +} + +template +void do_return(std::false_type /* is_void */, Promise& promise, Context ctx, Func && func, Args && ... args) +{ + promise.return_value(std::forward(func)(ctx, std::forward(args)...)); +} + + } template @@ -242,14 +272,14 @@ struct context private: - context(detail::fiber_frame * frame) : frame_(frame) {} + context(detail::context_frame * frame) : frame_(frame) {} template friend struct context; //template - friend struct detail::fiber_frame; + friend struct detail::context_frame; - detail::fiber_frame * frame_; + detail::context_frame * frame_; }; template, Args...> Func, typename StackAlloc> @@ -258,17 +288,17 @@ auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Arg auto sctx_ = salloc.allocate(); using promise_type = typename std::coroutine_traits::promise_type; - void * p = static_cast(sctx_.sp) - sizeof(detail::fiber_frame); - auto sz = sctx_.size - sizeof(detail::fiber_frame); + void * p = static_cast(sctx_.sp) - sizeof(detail::context_frame); + auto sz = sctx_.size - sizeof(detail::context_frame); - if (auto diff = reinterpret_cast(p) % alignof(detail::fiber_frame); diff != 0u) + if (auto diff = reinterpret_cast(p) % alignof(detail::context_frame); diff != 0u) { p = static_cast(p) - diff; sz -= diff; } boost::context::preallocated psc{p, sz, sctx_}; - auto f = new (p) detail::fiber_frame(args...); + auto f = new (p) detail::context_frame(args...); auto res = f->promise.get_return_object(); @@ -278,11 +308,11 @@ auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Arg struct invoker { - detail::fiber_frame * frame; + detail::context_frame * frame; mutable Func func; mutable std::tuple args; - invoker(detail::fiber_frame * frame, Func && func, Args && ... args) + invoker(detail::context_frame * frame, Func && func, Args && ... args) : frame(frame), func(std::forward(func)), args(std::forward(args)...) { } @@ -303,15 +333,8 @@ auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Arg auto ctx = frame->template get_context(); using return_type = decltype(std::forward(func)(ctx, std::forward(args_)...)); - if constexpr (std::is_void_v) - { - std::forward(func)(ctx, std::forward(args_)...); - frame->promise.return_void(); - } - else - frame->promise.return_value(std::forward(func)(ctx, std::forward(args_)...)); - - + detail::do_return(std::is_void{}, frame->promise, ctx, + std::forward(func), std::forward(args_)...); }, std::move(args)); } @@ -364,4 +387,4 @@ auto make_context(Func && func, Args && ... args) } -#endif //BOOST_COBALT_CONTEXT_HPP +#endif //BOOST_COBALT_EXPERIMENTAL_CONTEXT_HPP diff --git a/include/boost/cobalt/experimental/frame.hpp b/include/boost/cobalt/experimental/frame.hpp new file mode 100644 index 00000000..2220008b --- /dev/null +++ b/include/boost/cobalt/experimental/frame.hpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_COBALT_EXPERIMENTAL_FRAME_HPP +#define BOOST_COBALT_EXPERIMENTAL_FRAME_HPP + +#include +#include + +namespace boost::cobalt::experimental +{ + +template +struct frame +{ + void (*resume_) (frame *) = +[](frame * ff) { static_cast(ff)->resume();}; + void (*destroy_)(frame *) = +[](frame * ff) { static_cast(ff)->destroy();}; + typedef Promise promise_type; + Promise promise; + + template + frame(Args && ... args) : promise(std::forward(args)...) + { + } + +}; + + +} + +#endif //BOOST_COBALT_EXPERIMENTAL_FRAME_HPP diff --git a/include/boost/cobalt/experimental/yield_context.hpp b/include/boost/cobalt/experimental/yield_context.hpp new file mode 100644 index 00000000..800f6a63 --- /dev/null +++ b/include/boost/cobalt/experimental/yield_context.hpp @@ -0,0 +1,106 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_COBALT_EXPERIMENTAL_YIELD_CONTEXT_HPP +#define BOOST_COBALT_EXPERIMENTAL_YIELD_CONTEXT_HPP + +#include +#include + +#include +#include + +template +struct std::coroutine_handle> +{ + constexpr operator coroutine_handle<>() const noexcept { return coroutine_handle<>::from_address(address()); } + + constexpr explicit operator bool() const noexcept { return true; } + + constexpr bool done() const noexcept { return false; } + void operator()() const noexcept {} + + void resume() const noexcept {frame_->promiseresume();} + void destroy() const noexcept {frame_->destroy();} + + boost::asio::basic_yield_context & promise() const noexcept { return frame_->promise; } + + constexpr void* address() const noexcept { return frame_; } + + struct yield_context_frame : + boost::cobalt::experimental::frame> + { + using boost::cobalt::experimental::frame>::frame; + void resume() + { + lifetime.resume(); + } + void destroy() + { + // destroy the lifetime. + auto lf = std::move(lifetime); + } + + boost::asio::detail::spawn_handler_base lifetime{this->promise}; + }; + + coroutine_handle(yield_context_frame & frame) : frame_(&frame) {} + private: + yield_context_frame * frame_; +}; + +namespace boost::cobalt::experimental +{ + +template +auto await(Aw && aw, boost::asio::basic_yield_context ctx) +{ + if (!std::forward(aw).await_ready()) + { + + using ch = std::coroutine_handle>; + typename ch::yield_context_frame fr{std::move(ctx)}; + ch h{fr}; + ctx.spawned_thread_->suspend_with( + [&] + { + using rt = decltype(std::forward(aw).await_suspend(h)); + if constexpr (std::is_void_v) + std::forward(aw).await_suspend(h); + else if constexpr (std::is_same_v) + { + if (!std::forward(aw).await_suspend(h)) + ctx.spawned_thread_->resume(); + } + else + std::forward(aw).await_suspend(h).resume(); + } + ); + + } + return std::forward(aw).await_resume(); + +} + +template + requires requires (Aw && aw) {{std::forward(aw).operator co_await()} -> awaitable_type; } +auto await(Aw && aw, boost::asio::basic_yield_context ctx) +{ + return await(std::forward(aw).operator co_await(), std::move(ctx)); +} + +template +requires requires (Aw && aw) {{operator co_await(std::forward(aw))} -> awaitable_type; } +auto await(Aw && aw, boost::asio::basic_yield_context ctx) +{ + return await(operator co_await(std::forward(aw)), std::move(ctx)); +} + +} + + +#endif //BOOST_COBALT_EXPERIMENTAL_YIELD_CONTEXT_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d03ea243..63438057 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,7 +22,7 @@ add_test(NAME boost_cobalt_basic_tests COMMAND boost_cobalt_basic_tests) target_compile_features(boost_cobalt PUBLIC cxx_std_20) -add_executable(boost_cobalt_experimental test_main.cpp experimental/context.cpp) +add_executable(boost_cobalt_experimental test_main.cpp experimental/context.cpp experimental/yield_context.cpp) target_link_libraries(boost_cobalt_experimental Boost::cobalt Boost::unit_test_framework Boost::context) add_test(NAME boost_cobalt_experimental COMMAND boost_cobalt_experimental) diff --git a/test/experimental/context.cpp b/test/experimental/context.cpp index 1c73e564..0e0d6e75 100644 --- a/test/experimental/context.cpp +++ b/test/experimental/context.cpp @@ -12,11 +12,11 @@ using namespace boost::cobalt; -BOOST_AUTO_TEST_SUITE(fiber); +BOOST_AUTO_TEST_SUITE(context_); BOOST_AUTO_TEST_CASE(basics) { - boost::cobalt::experimental::detail::fiber_frame ff; + boost::cobalt::experimental::detail::context_frame ff; auto hh = std::coroutine_handle::from_address(&ff); BOOST_CHECK(!hh.done()); diff --git a/test/experimental/yield_context.cpp b/test/experimental/yield_context.cpp new file mode 100644 index 00000000..2c4919c2 --- /dev/null +++ b/test/experimental/yield_context.cpp @@ -0,0 +1,131 @@ +// +// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include + +#include "../test.hpp" + +using namespace boost::cobalt; + +BOOST_AUTO_TEST_SUITE(asio_yield_context); + +struct awaitable +{ + bool await_ready() { return ready;} + + bool await_suspend(std::coroutine_handle h) { this->h = h; return suspend;} + + int await_resume() {return value;} + + std::coroutine_handle h; + bool ready{false}; + bool suspend{true}; + int value; +}; + +awaitable aw; + +int test_impl(boost::asio::yield_context ctx) +{ + return experimental::await(aw, ctx); +} + +BOOST_AUTO_TEST_CASE(ready) +{ + boost::asio::io_context ctx; + + boost::asio::spawn(ctx, &test_impl, + [](std::exception_ptr ep, int i) + { + BOOST_CHECK_EQUAL(i, 42); + BOOST_CHECK(!ep); + }); + aw.ready = true; + aw.suspend = false; + aw.value = 42; + + ctx.run(); + + aw.h = {}; +} + +BOOST_AUTO_TEST_CASE(no_suspend) +{ + boost::asio::io_context ctx; + + boost::asio::spawn(ctx, &test_impl, + [](std::exception_ptr ep, int i) + { + BOOST_CHECK_EQUAL(i, 43); + BOOST_CHECK(!ep); + }); + aw.ready = false; + aw.suspend = false; + aw.value = 43; + + ctx.run(); + + aw.h = {}; +} + + +task t() +{ + co_return; +} + +struct dummy_aw +{ + bool await_ready() { return false;} + + std::coroutine_handle await_suspend(std::coroutine_handle h) { return h;} + void await_resume() {} +}; + +BOOST_AUTO_TEST_CASE(await) +{ + boost::asio::io_context ioc; + + boost::asio::spawn(ioc, + [&](boost::asio::yield_context ctx) + { + experimental::await(dummy_aw(), ctx); + experimental::await(t(), ctx); + }, + [](std::exception_ptr ep) + { + BOOST_CHECK(!ep); + }); + + ioc.run(); + +} + + +BOOST_AUTO_TEST_CASE(destroy) +{ + boost::asio::io_context ctx; + + boost::asio::spawn(ctx, &test_impl, + [](std::exception_ptr ep, int i) + { + BOOST_CHECK(false); + }); + aw.ready = false; + aw.suspend = true; + aw.h = nullptr; + ctx.run(); + // ASAN will complain if the yield_context doesn't get freed. + BOOST_CHECK(aw.h != nullptr); + aw.h.destroy(); +} + + +BOOST_AUTO_TEST_SUITE_END(); From e5f145e4d552e4f0a8d4d8bc7dc380df5f80cc51 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Sun, 28 Apr 2024 20:14:28 -0500 Subject: [PATCH 07/23] Add missing NO_LIB usage requirements. --- build/Jamfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/Jamfile b/build/Jamfile index 156eb6cc..ee7283d8 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -50,7 +50,7 @@ lib boost_cobalt : cobalt_sources : requirements BOOST_COBALT_SOURCE=1 shared:BOOST_COBALT_DYN_LINK=1 - [ requires + [ requires cxx20_hdr_concepts ] boost-container:/boost/container//boost_container @@ -64,6 +64,7 @@ lib boost_cobalt : usage-requirements boost-container:/boost/container//boost_container shared:BOOST_COBALT_DYN_LINK=1 + BOOST_COBALT_NO_LINK=1 [ check-target-builds $(config-binding:D)//cpp_lib_memory_resource cpp_lib_memory_resource From 9953526ae4bbe5908a3330f6bcf6a1af0c669ed1 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 1 May 2024 20:12:44 +0800 Subject: [PATCH 08/23] added noop utility closes #3 --- include/boost/cobalt/detail/generator.hpp | 3 ++ include/boost/cobalt/detail/promise.hpp | 7 +++ include/boost/cobalt/detail/task.hpp | 8 ++++ include/boost/cobalt/generator.hpp | 1 + include/boost/cobalt/noop.hpp | 55 +++++++++++++++++++++++ include/boost/cobalt/promise.hpp | 6 +++ include/boost/cobalt/task.hpp | 2 + test/generator.cpp | 3 ++ test/promise.cpp | 2 +- test/task.cpp | 3 +- 10 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 include/boost/cobalt/noop.hpp diff --git a/include/boost/cobalt/detail/generator.hpp b/include/boost/cobalt/detail/generator.hpp index ff34c819..5d795f6e 100644 --- a/include/boost/cobalt/detail/generator.hpp +++ b/include/boost/cobalt/detail/generator.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -92,6 +93,8 @@ struct generator_receiver : generator_receiver_base bool ready() { return exception || result || done; } + generator_receiver(noop n) : result(std::move(n.value)), done(true) {} + generator_receiver() = default; generator_receiver(generator_receiver && lhs) : generator_receiver_base{std::move(lhs.pushed_value)}, diff --git a/include/boost/cobalt/detail/promise.hpp b/include/boost/cobalt/detail/promise.hpp index 9b45aafb..1bf08851 100644 --- a/include/boost/cobalt/detail/promise.hpp +++ b/include/boost/cobalt/detail/promise.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,8 @@ struct promise_value_holder result.emplace(ret); static_cast*>(this)->set_done(); } + constexpr promise_value_holder() = default; + constexpr promise_value_holder(noop value) noexcept(std::is_nothrow_move_constructible_v) : result(std::move(value.value)) {} }; @@ -79,6 +82,9 @@ struct promise_value_holder } inline void return_void(); + + constexpr promise_value_holder() = default; + constexpr promise_value_holder(noop) {} }; @@ -113,6 +119,7 @@ struct promise_receiver : promise_value_holder } promise_receiver() = default; + promise_receiver(noop value) : promise_value_holder(std::move(value)), done(true) {} promise_receiver(promise_receiver && lhs) noexcept : promise_value_holder(std::move(lhs)), exception(std::move(lhs.exception)), done(lhs.done), awaited_from(std::move(lhs.awaited_from)), diff --git a/include/boost/cobalt/detail/task.hpp b/include/boost/cobalt/detail/task.hpp index 8aff2a58..b557e07a 100644 --- a/include/boost/cobalt/detail/task.hpp +++ b/include/boost/cobalt/detail/task.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -59,6 +60,9 @@ struct task_value_holder result.emplace(ret); static_cast*>(this)->set_done(); } + + constexpr task_value_holder() noexcept = default; + constexpr task_value_holder(noop n) noexcept(std::is_nothrow_move_constructible_v) : result(std::move(n.value)) {} }; template<> @@ -72,6 +76,9 @@ struct task_value_holder } inline void return_void(); + + constexpr task_value_holder() noexcept = default; + constexpr task_value_holder(noop n) noexcept {} }; @@ -114,6 +121,7 @@ struct task_receiver : task_value_holder promise->signal.emit(ct); } + task_receiver(noop n) : task_value_holder(std::move(n)), done(true) {} task_receiver() = default; task_receiver(task_receiver && lhs) : task_value_holder(std::move(lhs)), diff --git a/include/boost/cobalt/generator.hpp b/include/boost/cobalt/generator.hpp index 06ab1ebf..0c5fbc09 100644 --- a/include/boost/cobalt/generator.hpp +++ b/include/boost/cobalt/generator.hpp @@ -47,6 +47,7 @@ struct [[nodiscard]] generator generator(const generator &) = delete; generator& operator=(const generator &) = delete; + constexpr generator(noop n) : receiver_(std::move(n)){} private: template diff --git a/include/boost/cobalt/noop.hpp b/include/boost/cobalt/noop.hpp new file mode 100644 index 00000000..9c18e7da --- /dev/null +++ b/include/boost/cobalt/noop.hpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_COBALT_NOOP_HPP +#define BOOST_COBALT_NOOP_HPP + +#include +#include + +namespace boost::cobalt +{ + + +// tag::outline[] +// This is a tag type allowing the creation of promises or generators without creating a coroutine. +template +struct noop +{ + template + constexpr noop(Args && ... args) noexcept(std::is_nothrow_constructible_v) + : value(std::forward(args)...) + { + } + // end::outline[] + T value; + + constexpr static bool await_ready() {return true;} + template + constexpr static void await_suspend(std::coroutine_handle

) {} + constexpr T await_resume() {return std::move(value);} + + // tag::outline[] +}; +// end::outline[] + +template<> struct noop +{ + constexpr static bool await_ready() {return true;} + template + constexpr static void await_suspend(std::coroutine_handle

) {} + constexpr static void await_resume() {} +}; + + +template noop( T &&) -> noop; +template noop(const T & ) -> noop; +noop() -> noop; + +} + +#endif //BOOST_COBALT_NOOP_HPP diff --git a/include/boost/cobalt/promise.hpp b/include/boost/cobalt/promise.hpp index 9107c174..7e3b3019 100644 --- a/include/boost/cobalt/promise.hpp +++ b/include/boost/cobalt/promise.hpp @@ -41,6 +41,10 @@ struct [[nodiscard]] promise // end::outline[] /* tag::outline[] + // Create an already completed promimse + + static promise + // Get the return value. If !ready() this function has undefined behaviour. Return get(); end::outline[] */ @@ -60,6 +64,8 @@ struct [[nodiscard]] promise if (attached_) cancel(); } + + constexpr promise(noop n) : receiver_(std::move(n)), attached_(false) {} private: template friend struct detail::cobalt_promise; diff --git a/include/boost/cobalt/task.hpp b/include/boost/cobalt/task.hpp index f24bde86..3e8105a9 100644 --- a/include/boost/cobalt/task.hpp +++ b/include/boost/cobalt/task.hpp @@ -34,6 +34,8 @@ struct [[nodiscard]] task using promise_type = detail::task_promise; + constexpr task(noop n) : receiver_(std::move(n)){} + private: template friend struct detail::task_promise; diff --git a/test/generator.cpp b/test/generator.cpp index 2bafefc9..ed441afe 100644 --- a/test/generator.cpp +++ b/test/generator.cpp @@ -296,11 +296,14 @@ cobalt::generator detached_push() co_return i; } +cobalt::generator np() {return cobalt::noop(42);} + CO_TEST_CASE(detached_push_) { auto g = detached_push(); co_await g(1); + co_await np(); } BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file diff --git a/test/promise.cpp b/test/promise.cpp index c1fe8746..7af51306 100644 --- a/test/promise.cpp +++ b/test/promise.cpp @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(unwind) cobalt::promise return_(std::size_t ms) { - co_return 1234u; + return cobalt::noop(1234); } cobalt::promise return_(std::size_t ms, asio::executor_arg_t, diff --git a/test/task.cpp b/test/task.cpp index d98fb2f3..c01a4282 100644 --- a/test/task.cpp +++ b/test/task.cpp @@ -34,11 +34,12 @@ namespace cobalt::task test0() { - co_return; + return cobalt::noop(); } cobalt::task test2(int i) { + co_await cobalt::noop(); co_await test0(); co_return i; } From 7f334f21013577703798a00acd07523eea15681a Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 2 May 2024 15:45:48 +0800 Subject: [PATCH 09/23] added support for __cpp_sized_deallocation in custom coroutine allocations. (non-apple) clang is weird, as it allowed sized deallocations on coroutine promises even when not enabled otherwise. Since apple does not, this should fix using cobalt on apple. --- .github/workflows/ci.yml | 19 +++- include/boost/cobalt/detail/main.hpp | 23 +++++ include/boost/cobalt/detail/promise.hpp | 2 - include/boost/cobalt/detail/wrapper.hpp | 9 +- include/boost/cobalt/this_coro.hpp | 126 ++++++++++++++++++++---- 5 files changed, 154 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d3f44fa..6a044d0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,7 @@ jobs: cxxstd: "20,2b" os: macos-13 stdlib: libc++ + cobalt_pmr: boost-container runs-on: ${{matrix.os}} container: ${{matrix.container}} @@ -187,12 +188,16 @@ jobs: include: - os: ubuntu-22.04 shared: OFF + boost-container: 0 - os: ubuntu-22.04 shared: ON + boost-container: 0 - os: macos-13 shared: OFF + boost-container: 1 - os: macos-13 shared: ON + boost-container: 1 runs-on: ${{matrix.os}} @@ -234,7 +239,7 @@ jobs: run: | cd ../boost-root/libs/$LIBRARY/test/cmake_subdir_test mkdir __build__ && cd __build__ - cmake -DBUILD_SHARED_LIBS=${{matrix.shared}} .. + cmake -DBUILD_SHARED_LIBS=${{matrix.shared}} -DBOOST_COBALT_USE_BOOST_CONTAINER=${{matrix.boost-container}} .. cmake --build . ctest --output-on-failure --no-tests=error @@ -245,12 +250,16 @@ jobs: include: - os: ubuntu-22.04 shared: OFF + boost-container: 0 - os: ubuntu-22.04 shared: ON + boost-container: 0 - os: macos-13 shared: OFF + boost-container: 1 - os: macos-13 shared: ON + boost-container: 1 runs-on: ${{matrix.os}} @@ -293,7 +302,7 @@ jobs: run: | cd ../boost-root mkdir __build__ && cd __build__ - cmake -DBOOST_INCLUDE_LIBRARIES=$LIBRARY -DCMAKE_INSTALL_PREFIX=~/.local -DBUILD_SHARED_LIBS=${{matrix.shared}} .. + cmake -DBOOST_INCLUDE_LIBRARIES=$LIBRARY -DCMAKE_INSTALL_PREFIX=~/.local -DBUILD_SHARED_LIBS=${{matrix.shared}} -DBOOST_COBALT_USE_BOOST_CONTAINER=${{matrix.boost-container}} .. - name: Build run: | @@ -320,12 +329,16 @@ jobs: include: - os: ubuntu-22.04 shared: OFF + boost-container: 0 - os: ubuntu-22.04 shared: ON + boost-container: 0 - os: macos-13 shared: OFF + boost-container: 1 - os: macos-13 shared: ON + boost-container: 1 runs-on: ${{matrix.os}} @@ -367,7 +380,7 @@ jobs: run: | cd ../boost-root mkdir __build__ && cd __build__ - cmake -DBOOST_INCLUDE_LIBRARIES=$LIBRARY -DBUILD_TESTING=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} .. + cmake -DBOOST_INCLUDE_LIBRARIES=$LIBRARY -DBUILD_TESTING=ON -DBOOST_COBALT_USE_BOOST_CONTAINER=${{matrix.boost-container}} -DBUILD_SHARED_LIBS=${{matrix.shared}} .. - name: Build tests run: | diff --git a/include/boost/cobalt/detail/main.hpp b/include/boost/cobalt/detail/main.hpp index b8b5aa9c..1f2ae37c 100644 --- a/include/boost/cobalt/detail/main.hpp +++ b/include/boost/cobalt/detail/main.hpp @@ -51,6 +51,8 @@ struct main_promise : signal_helper, #if !defined(BOOST_COBALT_NO_PMR) inline static pmr::memory_resource * my_resource = pmr::get_default_resource(); + +#if defined(__cpp_sized_deallocation) void * operator new(const std::size_t size) { return my_resource->allocate(size); @@ -60,6 +62,27 @@ struct main_promise : signal_helper, { return my_resource->deallocate(raw, size); } +#else + void * operator new(const std::size_t size) + { + // embed the size at the end + constexpr auto sz = (std::max)(alignof(std::max_align_t), sizeof(std::size_t)); + auto data = my_resource->allocate(size + sz); + + return static_cast(data) + sz; + } + + void operator delete(void * data) + { + constexpr auto sz = (std::max)(alignof(std::max_align_t), sizeof(std::size_t)); + const auto size = *reinterpret_cast(static_cast(data) - sz); + + return my_resource->deallocate(data, size); + } +#endif + + + #endif std::suspend_always initial_suspend() noexcept {return {};} diff --git a/include/boost/cobalt/detail/promise.hpp b/include/boost/cobalt/detail/promise.hpp index 1bf08851..1fcb5474 100644 --- a/include/boost/cobalt/detail/promise.hpp +++ b/include/boost/cobalt/detail/promise.hpp @@ -18,8 +18,6 @@ #include - - #include #include diff --git a/include/boost/cobalt/detail/wrapper.hpp b/include/boost/cobalt/detail/wrapper.hpp index 63003094..41ae112c 100644 --- a/include/boost/cobalt/detail/wrapper.hpp +++ b/include/boost/cobalt/detail/wrapper.hpp @@ -40,11 +40,18 @@ struct partial_promise_base // clang: 96 8 16 return allocate_coroutine(size, asio::get_associated_allocator(token)); } - +#if defined(__cpp_sized_deallocation) void operator delete(void * raw, const std::size_t size) { deallocate_coroutine(raw, size); } +#else + void operator delete(void * raw) + { + deallocate_coroutine(raw); + } + +#endif }; diff --git a/include/boost/cobalt/this_coro.hpp b/include/boost/cobalt/this_coro.hpp index 77df24bf..2aba8c52 100644 --- a/include/boost/cobalt/this_coro.hpp +++ b/include/boost/cobalt/this_coro.hpp @@ -336,22 +336,58 @@ struct promise_memory_resource_base using allocator_type = pmr::polymorphic_allocator; allocator_type get_allocator() const {return allocator_type{resource};} +#if defined(__cpp_sized_deallocation) template static void * operator new(const std::size_t size, Args & ... args) { auto res = detail::get_memory_resource_from_args(args...); - const auto p = res->allocate(size + sizeof(pmr::memory_resource *), alignof(pmr::memory_resource *)); + const auto p = res->allocate(size + sizeof(std::max_align_t)); auto pp = static_cast(p); *pp = res; - return pp + 1; + return static_cast(p) + 1; } static void operator delete(void * raw, const std::size_t size) noexcept { - const auto p = static_cast(raw) - 1; - pmr::memory_resource * res = *p; - res->deallocate(p, size + sizeof(pmr::memory_resource *), alignof(pmr::memory_resource *)); + const auto p = static_cast(raw) - 1; + pmr::memory_resource * res = *reinterpret_cast(p); + res->deallocate(p, size + sizeof(std::max_align_t)); } +#else + template + static void * operator new(const std::size_t size, Args & ... args) + { + using tt = std::pair; + + // | memory_resource | size_t | | coroutine. + constexpr auto block_size = sizeof(tt) / sizeof(std::max_align_t) + + (sizeof(tt) % sizeof(std::max_align_t) ? 1 : 0); + + + auto res = detail::get_memory_resource_from_args(args...); + const auto p = res->allocate(size + (block_size * sizeof(std::max_align_t))); + new (p) tt(res, size); + return static_cast(p) + block_size; + } + + static void operator delete(void * raw) noexcept + { + using tt = std::pair; + + // | memory_resource | size_t | | coroutine. + constexpr auto block_size = sizeof(tt) / sizeof(std::max_align_t) + + (sizeof(tt) % sizeof(std::max_align_t) ? 1 : 0); + + const auto p = static_cast(raw) - block_size; + + const auto tp = *reinterpret_cast(p); + const auto res = tp.first; + const auto size = tp.second; + + res->deallocate(p, size + (block_size * sizeof(std::max_align_t))); + } +#endif + promise_memory_resource_base(pmr::memory_resource * resource = this_thread::get_default_resource()) : resource(resource) {} private: @@ -359,19 +395,22 @@ struct promise_memory_resource_base #endif }; +#if defined(__cpp_sized_deallocation) /// Allocate the memory and put the allocator behind the cobalt memory template void *allocate_coroutine(const std::size_t size, AllocatorType alloc_) { - using alloc_type = typename std::allocator_traits::template rebind_alloc; + using alloc_type = typename std::allocator_traits::template rebind_alloc; alloc_type alloc{alloc_}; - const std::size_t align_needed = size % alignof(alloc_type); - const std::size_t align_offset = align_needed != 0 ? alignof(alloc_type) - align_needed : 0ull; - const std::size_t alloc_size = size + sizeof(alloc_type) + align_offset; - const auto raw = std::allocator_traits::allocate(alloc, alloc_size); - new(raw + size + align_offset) alloc_type(std::move(alloc)); + const std::size_t aligned_size = size / sizeof(std::max_align_t) + + (size % sizeof(std::max_align_t) > 0 ? 1 : 0); + + const std::size_t alloc_size = sizeof(AllocatorType) / sizeof(std::max_align_t) + + (sizeof(AllocatorType) % sizeof(std::max_align_t) > 0 ? 1 : 0); + const auto raw = std::allocator_traits::allocate(alloc, alloc_size + aligned_size); + new(raw + aligned_size) alloc_type(std::move(alloc)); return raw; } @@ -379,20 +418,69 @@ void *allocate_coroutine(const std::size_t size, AllocatorType alloc_) template void deallocate_coroutine(void *raw_, const std::size_t size) { - using alloc_type = typename std::allocator_traits::template rebind_alloc; - const auto raw = static_cast(raw_); + using alloc_type = typename std::allocator_traits::template rebind_alloc; + const auto raw = static_cast(raw_); - const std::size_t align_needed = size % alignof(alloc_type); - const std::size_t align_offset = align_needed != 0 ? alignof(alloc_type) - align_needed : 0ull; - const std::size_t alloc_size = size + sizeof(alloc_type) + align_offset; - auto alloc_p = reinterpret_cast(raw + size + align_offset); + const std::size_t aligned_size = size / sizeof(std::max_align_t) + + (size % sizeof(std::max_align_t) > 0 ? 1 : 0); + const std::size_t alloc_size = sizeof(AllocatorType) / sizeof(std::max_align_t) + + (sizeof(AllocatorType) % sizeof(std::max_align_t) > 0 ? 1 : 0); + + auto alloc_p = reinterpret_cast(raw + aligned_size); auto alloc = std::move(*alloc_p); alloc_p->~alloc_type(); - using size_type = typename std::allocator_traits::size_type; - std::allocator_traits::deallocate(alloc, raw, static_cast(alloc_size)); + std::allocator_traits::deallocate(alloc, raw, aligned_size + alloc_size); } +#else + +/// Allocate the memory and put the allocator behind the cobalt memory +template +void *allocate_coroutine(const std::size_t size, AllocatorType alloc_) +{ + using alloc_type = typename std::allocator_traits::template rebind_alloc; + alloc_type alloc{alloc_}; + + const std::size_t aligned_size = size / sizeof(std::max_align_t) + + (size % sizeof(std::max_align_t) > 0 ? 1 : 0); + + const std::size_t alloc_size = sizeof(AllocatorType) / sizeof(std::max_align_t) + + (sizeof(AllocatorType) % sizeof(std::max_align_t) > 0 ? 1 : 0); + + const std::size_t size_size = sizeof(std::size_t) / sizeof(std::max_align_t) + + (sizeof(std::size_t) % sizeof(std::max_align_t) > 0 ? 1 : 0); + + + static_assert(alignof(std::max_align_t) >= sizeof(std::size_t)); + const auto raw = std::allocator_traits::allocate(alloc, alloc_size + aligned_size + size_size); + + new(raw) alloc_type(std::move(alloc)); + new(raw + alloc_size) std::size_t(aligned_size); + return raw + alloc_size + size_size; +} + +/// Deallocate the memory and destroy the allocator in the cobalt memory. +template +void deallocate_coroutine(void *raw_) +{ + using alloc_type = typename std::allocator_traits::template rebind_alloc; + const auto raw = static_cast(raw_); + + const std::size_t size_size = sizeof(std::size_t) / sizeof(std::max_align_t) + + (sizeof(std::size_t) % sizeof(std::max_align_t) > 0 ? 1 : 0); + const std::size_t aligned_size = *reinterpret_cast(raw - size_size); + const std::size_t alloc_size = sizeof(AllocatorType) / sizeof(std::max_align_t) + + (sizeof(AllocatorType) % sizeof(std::max_align_t) > 0 ? 1 : 0); + + auto alloc_p = reinterpret_cast(raw - alloc_size - size_size); + auto alloc = std::move(*alloc_p); + alloc_p->~alloc_type(); + std::allocator_traits::deallocate(alloc, raw - alloc_size - size_size, aligned_size + alloc_size + size_size); +} + + +#endif template struct enable_await_allocator From 3ab07fe23d388bf896f88d2d63c6015a2553f905 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Sat, 4 May 2024 23:28:09 -0500 Subject: [PATCH 10/23] Add missing import-search for cconfig/predef checks. --- build/Jamfile | 1 + 1 file changed, 1 insertion(+) diff --git a/build/Jamfile b/build/Jamfile index ee7283d8..523e919a 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -6,6 +6,7 @@ import os ; import feature ; +import-search /boost/config/checks ; import config : requires ; From 3b88b4c734125e2befb0535a8b6bc00d2d680130 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Sun, 5 May 2024 09:00:00 -0500 Subject: [PATCH 11/23] Add requires-b2 check to top-level build file. --- build.jam | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.jam b/build.jam index 1f4a10eb..2ee570c3 100644 --- a/build.jam +++ b/build.jam @@ -3,6 +3,8 @@ # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) +require-b2 5.1 ; + import project ; project /boost/cobalt From df750b72d7ce94a57940d13cd8cf8f3a6093e15e Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Mon, 13 May 2024 21:44:39 -0500 Subject: [PATCH 12/23] Update dependencies. --- build.jam | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.jam b/build.jam index 2ee570c3..4c389f0d 100644 --- a/build.jam +++ b/build.jam @@ -10,9 +10,11 @@ import project ; project /boost/cobalt : common-requirements /boost/asio//boost_asio + /boost/callable_traits//boost_callable_traits /boost/circular_buffer//boost_circular_buffer /boost/config//boost_config /boost/container//boost_container + /boost/context//boost_context /boost/core//boost_core /boost/intrusive//boost_intrusive /boost/leaf//boost_leaf From 4009635777aab0e25916f3868f5c277a6111037f Mon Sep 17 00:00:00 2001 From: Jonathan Stein Date: Wed, 27 Dec 2023 09:21:34 -0600 Subject: [PATCH 13/23] Fix -Wreorder flagging If using -Werror=reorder via some cmake build or otherwise, this gets flagged. --- include/boost/cobalt/detail/generator.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/cobalt/detail/generator.hpp b/include/boost/cobalt/detail/generator.hpp index 5d795f6e..6adc29e2 100644 --- a/include/boost/cobalt/detail/generator.hpp +++ b/include/boost/cobalt/detail/generator.hpp @@ -98,9 +98,9 @@ struct generator_receiver : generator_receiver_base generator_receiver() = default; generator_receiver(generator_receiver && lhs) : generator_receiver_base{std::move(lhs.pushed_value)}, - exception(std::move(lhs.exception)), done(lhs.done), + exception(std::move(lhs.exception)), result(std::move(lhs.result)), - result_buffer(std::move(lhs.result_buffer)), + result_buffer(std::move(lhs.result_buffer)), done(lhs.done), awaited_from(std::move(lhs.awaited_from)), yield_from{std::move(lhs.yield_from)}, lazy(lhs.lazy), reference(lhs.reference), cancel_signal(lhs.cancel_signal) From 9502d092f5004d8b5b7a8ad4e8c93ac37f64fc37 Mon Sep 17 00:00:00 2001 From: Klemens Date: Mon, 27 May 2024 09:54:59 +0800 Subject: [PATCH 14/23] added move support for channels Closes #183 --- include/boost/cobalt/channel.hpp | 8 +++++- include/boost/cobalt/impl/channel.hpp | 35 +++++++++++++++++++-------- test/channel.cpp | 29 +++++++++++++++++++++- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/include/boost/cobalt/channel.hpp b/include/boost/cobalt/channel.hpp index c408b3b8..d1854595 100644 --- a/include/boost/cobalt/channel.hpp +++ b/include/boost/cobalt/channel.hpp @@ -99,7 +99,11 @@ struct channel struct write_op : intrusive::list_base_hook > { channel * chn; - variant2::variant ref; + using ref_t = std::conditional_t< + std::is_copy_constructible_v, + variant2::variant, + T*>; + ref_t ref; boost::source_location loc; bool cancelled = false, direct = false; asio::cancellation_slot cancel_slot{}; @@ -131,10 +135,12 @@ struct channel public: read_op read(const boost::source_location & loc = BOOST_CURRENT_LOCATION) {return read_op{{}, this, loc}; } write_op write(const T && value, const boost::source_location & loc = BOOST_CURRENT_LOCATION) + requires std::is_copy_constructible_v { return write_op{{}, this, &value, loc}; } write_op write(const T & value, const boost::source_location & loc = BOOST_CURRENT_LOCATION) + requires std::is_copy_constructible_v { return write_op{{}, this, &value, loc}; } diff --git a/include/boost/cobalt/impl/channel.hpp b/include/boost/cobalt/impl/channel.hpp index 1b1abc21..21192c13 100644 --- a/include/boost/cobalt/impl/channel.hpp +++ b/include/boost/cobalt/impl/channel.hpp @@ -118,10 +118,15 @@ std::coroutine_handle channel::read_op::await_suspend(std::coroutine_ha auto & op = chn->write_queue_.front(); op.transactional_unlink(); op.direct = true; - if (op.ref.index() == 0) - direct = std::move(*variant2::get<0>(op.ref)); + if constexpr (std::is_copy_constructible_v) + { + if (op.ref.index() == 0) + direct = std::move(*variant2::get<0>(op.ref)); + else + direct = *variant2::get<1>(op.ref); + } else - direct = *variant2::get<1>(op.ref); + direct = std::move(*op.ref); BOOST_ASSERT(op.awaited_from); asio::post(chn->executor_, std::move(awaited_from)); return op.awaited_from.release(); @@ -171,7 +176,7 @@ system::result channel::read_op::await_resume(const struct as_result_tag & asio::post(chn->executor_, std::move(op.awaited_from)); } } - return {system::in_place_value, value}; + return {system::in_place_value, std::move(value)}; } template @@ -213,10 +218,15 @@ std::coroutine_handle channel::write_op::await_suspend(std::coroutine_h cancel_slot.clear(); auto & op = chn->read_queue_.front(); op.transactional_unlink(); - if (ref.index() == 0) - op.direct = std::move(*variant2::get<0>(ref)); + if constexpr (std::is_copy_constructible_v) + { + if (ref.index() == 0) + op.direct.emplace(std::move(*variant2::get<0>(ref))); + else + op.direct.emplace(*variant2::get<1>(ref)); + } else - op.direct = *variant2::get<1>(ref); + op.direct.emplace(std::move(*ref)); BOOST_ASSERT(op.awaited_from); direct = true; @@ -250,10 +260,15 @@ system::result channel::write_op::await_resume(const struct as_result_ if (!direct) { BOOST_ASSERT(!chn->buffer_.full()); - if (ref.index() == 0) - chn->buffer_.push_back(std::move(*variant2::get<0>(ref))); + if constexpr (std::is_copy_constructible_v) + { + if (ref.index() == 0) + chn->buffer_.push_back(std::move(*variant2::get<0>(ref))); + else + chn->buffer_.push_back(*variant2::get<1>(ref)); + } else - chn->buffer_.push_back(*variant2::get<1>(ref)); + chn->buffer_.push_back(std::move(*ref)); } if (!chn->read_queue_.empty()) diff --git a/test/channel.cpp b/test/channel.cpp index 0a2101f0..1652c0a8 100644 --- a/test/channel.cpp +++ b/test/channel.cpp @@ -287,7 +287,34 @@ CO_TEST_CASE(reader) } +} + + +BOOST_AUTO_TEST_SUITE_END(); + +namespace boost::cobalt +{ + +struct move_only +{ + move_only() {} + move_only(move_only &&) {} + move_only& operator=(move_only &&) {return * this;} +}; + +template struct channel; + +CO_TEST_CASE(unique) +{ + std::unique_ptr p{new int(42)}; + auto pi = p.get(); + cobalt::channel> c{1u}; + + co_await c.write(std::move(p)); + auto p2 = co_await c.read(); + BOOST_CHECK(p == nullptr); + BOOST_CHECK(p2.get() == pi); } -BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file +} From 761691080519321c268e914543e3e13172cf478d Mon Sep 17 00:00:00 2001 From: Klemens Date: Mon, 27 May 2024 10:12:14 +0800 Subject: [PATCH 15/23] fixed ctor so any works. Closes #182 --- include/boost/cobalt/impl/channel.hpp | 2 +- test/channel.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/boost/cobalt/impl/channel.hpp b/include/boost/cobalt/impl/channel.hpp index 21192c13..c6c18eaa 100644 --- a/include/boost/cobalt/impl/channel.hpp +++ b/include/boost/cobalt/impl/channel.hpp @@ -22,7 +22,7 @@ inline channel::channel( std::size_t limit, executor executor, pmr::memory_resource * resource) - : buffer_(limit, resource), executor_(executor) {} + : buffer_(limit, pmr::polymorphic_allocator(resource)), executor_(executor) {} #else template inline channel::channel( diff --git a/test/channel.cpp b/test/channel.cpp index 1652c0a8..54fe6b21 100644 --- a/test/channel.cpp +++ b/test/channel.cpp @@ -16,6 +16,7 @@ #include "test.hpp" #include +#include namespace cobalt = boost::cobalt; @@ -317,4 +318,12 @@ CO_TEST_CASE(unique) BOOST_CHECK(p2.get() == pi); } +CO_TEST_CASE(any) +{ + cobalt::channel c{1u}; + co_return ; +} + + + } From d5abc2e8e92d1bb2801eaf4c7f2bdfdb99ef38d3 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Wed, 29 May 2024 22:13:35 +0800 Subject: [PATCH 16/23] added noinline ot channel functions when compiling for windows. --- include/boost/cobalt/channel.hpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/boost/cobalt/channel.hpp b/include/boost/cobalt/channel.hpp index d1854595..98e71176 100644 --- a/include/boost/cobalt/channel.hpp +++ b/include/boost/cobalt/channel.hpp @@ -134,20 +134,37 @@ struct channel boost::intrusive::list > write_queue_; public: read_op read(const boost::source_location & loc = BOOST_CURRENT_LOCATION) {return read_op{{}, this, loc}; } + +#if defined(BOOST_WINDOWS_API) + BOOST_NOINLINE +#endif write_op write(const T && value, const boost::source_location & loc = BOOST_CURRENT_LOCATION) requires std::is_copy_constructible_v { return write_op{{}, this, &value, loc}; } + +#if defined(BOOST_WINDOWS_API) + BOOST_NOINLINE +#endif write_op write(const T & value, const boost::source_location & loc = BOOST_CURRENT_LOCATION) requires std::is_copy_constructible_v { return write_op{{}, this, &value, loc}; } + + +#if defined(BOOST_WINDOWS_API) + BOOST_NOINLINE +#endif write_op write( T && value, const boost::source_location & loc = BOOST_CURRENT_LOCATION) { return write_op{{}, this, &value, loc}; } + +#if defined(BOOST_WINDOWS_API) + BOOST_NOINLINE +#endif write_op write( T & value, const boost::source_location & loc = BOOST_CURRENT_LOCATION) { return write_op{{}, this, &value, loc}; From 2f07dfbb8e5aa93a0f7a0b7163e805748b913a57 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 30 May 2024 08:01:34 +0800 Subject: [PATCH 17/23] removed move_only template inst from channel.cpp --- test/channel.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/channel.cpp b/test/channel.cpp index 54fe6b21..4eec322e 100644 --- a/test/channel.cpp +++ b/test/channel.cpp @@ -296,15 +296,6 @@ BOOST_AUTO_TEST_SUITE_END(); namespace boost::cobalt { -struct move_only -{ - move_only() {} - move_only(move_only &&) {} - move_only& operator=(move_only &&) {return * this;} -}; - -template struct channel; - CO_TEST_CASE(unique) { std::unique_ptr p{new int(42)}; From 86c8f15407b99ff091f3686a61b25465a0d30353 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 30 May 2024 23:33:27 +0800 Subject: [PATCH 18/23] Declared test targets with EXCLUDE_FROM_ALL Closes #181. --- test/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 63438057..c86699ce 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,12 +3,12 @@ if(NOT TARGET tests) set_property(TARGET tests PROPERTY FOLDER _deps) endif() -add_library(boost_cobalt_static_tests concepts.cpp util.cpp) +add_library(boost_cobalt_static_tests EXCLUDE_FROM_ALL concepts.cpp util.cpp) target_link_libraries(boost_cobalt_static_tests Boost::cobalt) -add_executable(boost_cobalt_main main.cpp) -add_executable(boost_cobalt_main_compile main_compile.cpp) -add_executable(boost_cobalt_basic_tests +add_executable(boost_cobalt_main EXCLUDE_FROM_ALL main.cpp) +add_executable(boost_cobalt_main_compile EXCLUDE_FROM_ALL main_compile.cpp) +add_executable(boost_cobalt_basic_tests EXCLUDE_FROM_ALL async_for.cpp test_main.cpp promise.cpp with.cpp op.cpp handler.cpp join.cpp race.cpp this_coro.cpp leaf.cpp channel.cpp generator.cpp run.cpp task.cpp gather.cpp wait_group.cpp wrappers.cpp left_race.cpp strand.cpp fork.cpp thread.cpp any_completion_handler.cpp detached.cpp monotonic_resource.cpp sbo_resource.cpp) @@ -19,10 +19,8 @@ target_link_libraries(boost_cobalt_basic_tests Boost::cobalt Boost::unit_test_f add_test(NAME boost_cobalt_main COMMAND boost_cobalt_main) add_test(NAME boost_cobalt_basic_tests COMMAND boost_cobalt_basic_tests) -target_compile_features(boost_cobalt PUBLIC cxx_std_20) - -add_executable(boost_cobalt_experimental test_main.cpp experimental/context.cpp experimental/yield_context.cpp) +add_executable(boost_cobalt_experimental EXCLUDE_FROM_ALL test_main.cpp experimental/context.cpp experimental/yield_context.cpp) target_link_libraries(boost_cobalt_experimental Boost::cobalt Boost::unit_test_framework Boost::context) add_test(NAME boost_cobalt_experimental COMMAND boost_cobalt_experimental) From a9ebaf952d49818d8e41e9fb54ae70323cb81326 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 30 May 2024 22:46:59 +0800 Subject: [PATCH 19/23] added generate-diagram option & cache --- CMakeLists.txt | 2 +- doc/Jamfile | 2 +- doc/background/lazy_eager.adoc | 19 ++++++++++++++----- doc/background/stackless.adoc | 17 +++++++++++++++-- doc/images/awaitables.png | Bin 0 -> 15339 bytes doc/images/generators1.png | Bin 0 -> 22753 bytes doc/images/generators2.png | Bin 0 -> 22753 bytes doc/images/lazy_eager1.png | Bin 0 -> 29519 bytes doc/images/lazy_eager2.png | Bin 0 -> 25196 bytes doc/images/stackless1.png | Bin 0 -> 10868 bytes doc/images/stackless2.png | Bin 0 -> 25102 bytes doc/index.adoc | 1 + doc/primer/awaitables.adoc | 9 +++++++-- doc/reference/generators.adoc | 17 +++++++++++++++-- 14 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 doc/images/awaitables.png create mode 100644 doc/images/generators1.png create mode 100644 doc/images/generators2.png create mode 100644 doc/images/lazy_eager1.png create mode 100644 doc/images/lazy_eager2.png create mode 100644 doc/images/stackless1.png create mode 100644 doc/images/stackless2.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 776cff46..fa38d1d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,7 @@ else() file(GLOB_RECURSE ADOC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.adoc) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html - COMMAND asciidoctor ${CMAKE_CURRENT_SOURCE_DIR}/doc/index.adoc --require asciidoctor-diagram --require asciidoctor-multipage -b multipage_html5 -o ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html + COMMAND asciidoctor ${CMAKE_CURRENT_SOURCE_DIR}/doc/index.adoc --require asciidoctor-diagram --require asciidoctor-multipage -b multipage_html5 -a generate-diagram -o ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html DEPENDS ${ADOC_FILES}) add_custom_target(boost_cobalt_doc DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html) diff --git a/doc/Jamfile b/doc/Jamfile index b6164133..19564375 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -4,7 +4,7 @@ import asciidoctor ; -html index.html : index.adoc : "-r asciidoctor-diagram" ; +html index.html : index.adoc ; install html_ : index.html : html ; diff --git a/doc/background/lazy_eager.adoc b/doc/background/lazy_eager.adoc index 4bb6865e..889d37c9 100644 --- a/doc/background/lazy_eager.adoc +++ b/doc/background/lazy_eager.adoc @@ -37,8 +37,8 @@ Coro Done resumed twice ---- - -[mermaid] +ifdef::generate-diagram[] +[mermaid, target=lazy_eager1] ---- sequenceDiagram participant main; @@ -54,6 +54,11 @@ sequenceDiagram lazy->>main: co_return Note left of main: "resumed twice" ---- +endif::[] + +ifndef::generate-diagram[] +image::{docdir}/images/lazy_eager1.png[] +endif::[] Whereas an eager coro would look like this: @@ -90,8 +95,8 @@ Coro Done ---- - -[mermaid] +ifdef::generate-diagram[] +[mermaid, target=lazy_eager2] ---- sequenceDiagram participant main; @@ -104,5 +109,9 @@ sequenceDiagram Note right of lazy: "Coro done" lazy->>main: co_return Note left of main: "resumed once" +---- +endif::[] ----- \ No newline at end of file +ifndef::generate-diagram[] +image::{docdir}/images/lazy_eager2.png[] +endif::[] diff --git a/doc/background/stackless.adoc b/doc/background/stackless.adoc index adbbb8b8..e22c44ac 100644 --- a/doc/background/stackless.adoc +++ b/doc/background/stackless.adoc @@ -30,7 +30,8 @@ main() bar() ---- -[mermaid] +ifdef::generate-diagram[] +[mermaid, target=stackless1] ---- sequenceDiagram main->>+foo: call @@ -38,6 +39,11 @@ sequenceDiagram bar->>-foo: return foo->>-main: return ---- +endif::[] + +ifndef::generate-diagram[] +image::{docdir}/images/stackless1.png[] +endif::[] Coroutines can be implemented a stackful, which means that it allocates a fixes chunk of memory and stacks function frames similar to a thread. C++20 coroutines are stackless, i.e. they only allocate their own frame and use the callers stack on resumption. Using our previous example: @@ -75,7 +81,9 @@ main() f$example() ---- -[mermaid] + +ifdef::generate-diagram[] +[mermaid, target=stackless2] ---- sequenceDiagram participant main @@ -89,5 +97,10 @@ sequenceDiagram main-->>example: resume example->>-main: co_return ---- +endif::[] + +ifndef::generate-diagram[] +image::{docdir}/images/stackless2.png[] +endif::[] The same applies if a coroutine gets moved accross threads. \ No newline at end of file diff --git a/doc/images/awaitables.png b/doc/images/awaitables.png new file mode 100644 index 0000000000000000000000000000000000000000..82808865a6df636ccbb6499aa924d958b951587b GIT binary patch literal 15339 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 MR+g%ecpmsa0Hm*{qyPW_ literal 0 HcmV?d00001 diff --git a/doc/images/generators1.png b/doc/images/generators1.png new file mode 100644 index 0000000000000000000000000000000000000000..ed191ab3b3ccbd7a645003a3815edcdccd57f001 GIT binary patch literal 22753 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 v9G^|Bgq_^__qgNI)i>F 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 Dh_rT| literal 0 HcmV?d00001 diff --git a/doc/images/lazy_eager2.png b/doc/images/lazy_eager2.png new file mode 100644 index 0000000000000000000000000000000000000000..bec78798f84466cad4e6f52ba3d68efe175080d6 GIT binary patch literal 25196 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≺)|f(;wVKHUgv4x zaK?EJP-p<^LK05?kaWB0bEj^~o0nkYa8 z!e&c->(2{BAqhoYnV>JjCBwA;fU(gnwSpausDlQ}?2C;WsF3IA%*@OHfb^<(&wCG+ z4A37qtTL)Sc4nc}3lA}B5qu8Z>`YjkxMaCd4t9`V1z%NAgsm%gm zVV`OJO9eAfN_GhLIvb-jSfIf{e~!iv-q=kum_UWR@fcVM37^-Pf358OT63-q12HCI z3;`~jI(=Fp?k6rXG814&T|-0MMzA*U1r{_^@Njm917-*bp@j~!U%K>l70sU3qjdki zGIS;+EG!k409bB>T68wL1j7tL0N=el(aee-s&M9A4mmS39w3}1*})3K0PoHYSM7)} zE4hTljdcxo>y>e6YjC!%?Qo*lZh)xhH4QCqYliF!DK?OQ9r5oF&9pPyAA6)U;ZFU z3Z8LuG*Y`T&0?Xl^ynLKS>U!I`5W{#IUADh@;;Yf-4`DDu4dRn86pG^Tv8jU_IRa# z+1Hf9vf$gPPv6H;$rLMbN8&bnaC_iWCJD=}nBNbV3P`2uKSG z3WO3MTu_RjM7lI19TI~G(n5)f2#81tO&|mXq>~#up=7_=-Pzgs&(445?u&h!$@~%y z-*V39Jm<-eWf6|wH){cyF$Bi|n`{444|FCHU{!*A+?_4N{+Fv?>XurZxM^9y!sdk` z*O%Fkv1I#bC!PSHk~sj)I35=jWp~5mMedM#=R-%X&yQ1bXeD|NHC(GsvhxUg@CG0j zsP~69A7dvUAJ?fO4;_IXpm4w6zt{fk^5!qs=m-3lE}C~~ZeRcx0#7`k$I#>!+{qNj ztlqnK^PAM!S2fGaT#=3zb=5A@``86{YHBftlT`+J@mv4^AdV8V!*SEO7PhgnQBl$= zeKO4#CVkc7SGU>3zUg*D5cctmtUHXd2^3;tS;-dynX(cv1Mv*kH9dvy*d5v`JE$X&ic z|AMj1jY0`e+8#IfVpZGI1`)0-=7*TMQI|a6OLx}CbEd5nJ&v`sqJP$zi;Hevoth(} z1A6r(RYVGBhTfyy-)FufX#|Q{O^fsLTUV0jB)BQhAAnI|`mt5k#8WI(15<+kG)j1Y z?MihLbk|zX&JNYOg3d&?@R&bUf7nOdkl1aQE|-|yWQelAf%DWgM=GWPJjQY)OPune zF@|$q(Vh{G914Bw&`<9L%)x7-2p9JRlJm%k3tMYH=r8yhh49&SZO8#vDqE1~QDWef zdwQA$+G-pMg5aOA;~8YbQ%h*XLkR`_IFn}*z?i7;w<|^Ytrb-o!Fc2#zD(+zTsJ6h zQnObx*Y9Q(J)c36T;6pg6?D?*y}Z1qX7ZyZQvHIO+!`-B|qL`ip|dNPT(M zq0QN@E#j<*fR?2j#^{q8OeC~t%VG;@2ATi7I5|L5tBA(traqqlHOf3BJMKmmH zH95(0eHJ_ja3fiDe5UyJO`p!Nz$yvPb_i`IbAUlAbWx6abVbGOjw-ZN6iG4#8IIbD z%{=2lBR{XBINtJo0{iasGYtzP9NdnWJm z)?O-TpU2-m8nAClFR1qBC8WbSBlY_iECHlKhEOKlOo%ei`jINj)EvLutx8L-+-A?8qB}+tUs}8N#=yr;R=<-v4{lI^~}=E{hossB#;4 z2j;TtVCs1V#a?lHonC*O=WOd_KnS0J2){>s0{$G3oH*7WFLeriB2gM}>!~ie+1nC6 z*4{A+wxL`Fo1|>h8;z1yDPK96lah&31%{f&9ts;rqM=KIXI+)9ncDHC(>=q zZEa(#_iClO!GH&+Du(YA+JDs}4NTq@Lc~dqM)P~;>ZwItO+#{nitY!f-ln&yS|L~m zGcf=mP@8Q>#5A7^d1zoXAsHEJNhU8&jMme`N7m7eOwZ|klzaaMFNWFC`zO4pNn@^^ zNl9XoQDNG+bB(pv0Vm+ISIld(HR5^#bc%-+BAqWPa~l}Y$#z{P@DeeN<86xyeFXvn zrMvQ`pC=ed=0aD0ZU=?w2_b>*IcAd%cNOilXe6D$!?hg1A}H!2kDf8QF;;DQo>es) zIp>i&q}!P}(El7VX5r?Rprllx;xRNY%;@Ge>Xlc$L>Z(NAg9Qe*~gtR`oSA6E-vGm z|Ce_((uh+_8Y5r{G-fgerhzR^ZwG7h`fC%rTs&0PbBHrX1t77oIyA^{Ut8~aB@5Sx z=HHXqR+y;;ozzuwOA8Su!*Awa4x`W( z5UeX9fGCV!n%yH6t+k6-6*Z|_g=6j}z;l-0lp!4;XQNR)WSj7CJ(L`PnbFvcGJqX? z)feZ6>!RS{!Qcr0nYH)wvFRFR{JUjpPV(#b550Q<(=9FU-dSEb%6rJ;qi|G_V;$|Avj(Y+c>Cx9*=?|SyhwZ)3XCExpWyICh{6LTH@4j}gaiAK%zrURx>FdlWRxE{Jp%pO2PJvK5 z$Xy!lzD7ztl9fnFX6j-ROhYXFrto*5#49%C9sBMJj{yzszu5nOb^o(dhKhsrfBbls zo?beIxOD`ud8r0?U^!dLVkNzzcTu9Ch!n_DVW%x`U^(vP;p7cTnqsrX%zFC=NR_hftLVD{XNO!JPKwun=Dg03Yl?c* zQZh>>R2#1P_0x$D@dqQD|J>efK;{l@?)FAdw?3;aC3ZSZ8TUK_;|DuQPBJ*B8E3o2 zQ|)w{PQe)cX5xE=V%tf#U{nx0QERij{;Sf&#+Iak689crC$q)5ZDL*aRfS_BD#tn2 zEa`BZv~`HZ${kyxFErp!=jfx>w&T#Hp8YK!)u;SZ7t5dHCZNMh`=g+!&p+ixS|i69 z+l38Z_6uQf$d$-MLmm+cL{_|9j==FXb#fjIuEf4@(v8IX3Hx6w*qV@1qN1qAksK z?Pa5KrsW;F2Z0af8<6)(O$+U7DbUPTV+8FiUu-_q@{zt~^dpJq#xXz&|Btl$zhq2* z)x`Qg_m194GTNnX_U9T@-cH{)e&$z@bvyUt!@=%gC&3(laDWv5#>bXRePV&5BA_M4 zark%X{Q@NhF>cJw3CX$9x~G{ckIl0#KYi;>6>emFqV(zWXKrP0mW*LS-e5MJ$Y7!mzDB1(fxVRB{Gil)Jw4#Ol$1TCs8QHfTb)!5 z4@0F@`{>}mRqAwCb|p|=Xw`R-c8!w72MMpg_NK{)sJMSUskJ`ju}IzaGZGaxTTrNf z6?YAbxk?Ksn|cl0?NOz@qWow%Dq-~S5Vr4YPR4MZvZcu7f)d{;**R)GnYmgAxtZRC zxT68cn*~-p@W8~ce{cbU@9+q?MIjOs&4IYtd!CS;EvmKJ%?pGBpp2jEEo2+jh}!NA zdyUNE37KHD3eYJpK7m2A($aQ!>YmmHxAiWQN5TleL8!jI3$W8oyBT8kPV%IPNb&lj zhAb$X&CM?YZC89!a`+XOI>rQqZ94{C`AO`n-*AMHF;5dj)#QxS(JBTamqQD|SVo%v zmTuG?ki_WPSrZ2MBX9=#`WR0FuZN$y0hl}#+YDp;*Z{L$a$;(}*TFPdYl2lB^`JWy zLnXa2#2n}0fm#nd{vrFDG*}h8xT0IZMjC7W4@!RZM?5uWI(y>+Lw+I!c;vgk{y z1i*0fnOL_!lyJf1a zW^Jmot4g%1G$h#gST~}1zJ_e;+NHc`LNtcZUX?k9%+gpUz@9M+wY|6-C+D{lR0Mcz zt_?inbb1HB2c0tQEpO+$yu1QFw6AhHM-rxCmIw;YlNH`nBsJ8}2H0vjBuxg?XZXZ& zxvczr%C@scugyutPfgl)C5V+8rUe7RM}MiM4H$moF$DC8P6X;IBz+7AhO!WI)g#Ol zTdJ-h@Daapod~m$mYEd+;j$GHf~a72+Ca&h@*GxGW`<^Bz)lDR(}Mk@b;>CztHv)` zgx5`dcqpq^xvK#rP~X3&m%6k9LF|JDAW#0_wRa-AFqL_ zEaQ}C-L^HfuaYWO+%}H=xAGx6dD>y#1F1IGWMw=b?lA{#_V0oPU-aZjGkc#g7?=2d zLm1>kXur+qptdsVb0U&l!Pr%JdA#n{{U08=O3I;Jgij6X4w~4J;>d}EcuM!$_R(}_ z9iNWm6{VO3la)Hc71cw>3J9JQ81s9KiX$fYYI(iTyY4?S`xl--2gf0>2)xpZyyFUS0F_aGdyp@89}Nw@22d zrrmNpQ6s%P_UN?ys*F^=ZaNgzCcTZrnMDg?$T+_tX>E+m#2@x{t(wc*64;n`u`3h) zcCM8oc=4%c6sWd#$EnHS)I@lRehp$N)Q9~ME|vCo1TBJ~S$K#V`<4`pJfk2)wpm_j z7np8$o|!z*vfQS)71YuathAH#e#hs)k$>D_pIcF4ARK%e|M_P5tB{0soGjWx)(deg Q1`p*hGBmkbrhhx~Z-ZYaApigX literal 0 HcmV?d00001 diff --git a/doc/images/stackless1.png b/doc/images/stackless1.png new file mode 100644 index 0000000000000000000000000000000000000000..d5a3d90bedd4b1bf0e366b0258870aeb72cb2d82 GIT binary patch literal 10868 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 literal 0 HcmV?d00001 diff --git a/doc/index.adoc b/doc/index.adoc index 0b12613e..e00a1676 100644 --- a/doc/index.adoc +++ b/doc/index.adoc @@ -11,6 +11,7 @@ Version 0.1, 29.01.2023 :source-language: c++ :example-caption: Example +:imagesdir: {docdir}/images :leveloffset: +1 diff --git a/doc/primer/awaitables.adoc b/doc/primer/awaitables.adoc index a8ea0f7c..e5635aa6 100644 --- a/doc/primer/awaitables.adoc +++ b/doc/primer/awaitables.adoc @@ -19,8 +19,8 @@ NOTE: Type will be implicitly converted into an awaitable if there is an `operat This documentation will use `awaitable` to include these types, and "actual_awaitable" to refer to type conforming to the above prototype. - -[mermaid] +ifdef::generate-diagram[] +[mermaid, target=awaitables] ---- flowchart TD aw{await_ready?} @@ -28,6 +28,11 @@ flowchart TD aw -->|false| as[await_suspend] as -->|Resume| ar ---- +endif::[] + +ifndef::generate-diagram[] +image::{docdir}/images/awaitables.png[] +endif::[] In a `co_await` expression the waiting coroutine will first invoke `await_ready` to check if the coroutine needs to suspend. diff --git a/doc/reference/generators.adoc b/doc/reference/generators.adoc index 69afb685..88eb7fc7 100644 --- a/doc/reference/generators.adoc +++ b/doc/reference/generators.adoc @@ -33,7 +33,9 @@ Which will generate the following output In coro 3 In main 4 -[mermaid] + +ifdef::generate-diagram[] +[mermaid, target=generators1] ---- sequenceDiagram participant main; @@ -47,6 +49,11 @@ sequenceDiagram example->>main: co_return 3 Note left of main: "In main 4" ---- +endif::[] + +ifndef::generate-diagram[] +image::{docdir}/images/generators1.png[] +endif::[] Values can be pushed into the generator, when `Push` (the second template parameter) is set to non-void: @@ -117,7 +124,8 @@ Which will generate the following output In coro 3 In main 4 -[mermaid] +ifdef::generate-diagram[] +[mermaid, target=generators2] ---- sequenceDiagram participant main; @@ -131,6 +139,11 @@ sequenceDiagram example->>main: co_return 3 Note left of main: "In main 4" ---- +endif::[] + +ifndef::generate-diagram[] +image::{docdir}/images/generators2.png[] +endif::[] [#generator-executor] === Executor From 5939e0b914a41f1b2db8f5d439d69503065c7b76 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Fri, 31 May 2024 19:11:27 +0800 Subject: [PATCH 20/23] Cleaned up CML. Closes #135. --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa38d1d3..5a51d528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,6 @@ if (NOT BOOST_COBALT_IS_ROOT) Boost::callable_traits Boost::circular_buffer Boost::config - Boost::container Boost::core Boost::intrusive Boost::leaf @@ -138,7 +137,6 @@ else() if (BOOST_COBALT_USE_BOOST_CONTAINER) find_package(Boost REQUIRED container) endif() - include_directories(include) endif() add_library(boost_cobalt From 1176df0693877e6efc34433e29aee4e5b1ba349f Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Fri, 14 Jun 2024 11:33:55 -0500 Subject: [PATCH 21/23] Bump B2 require to 5.2 --- build.jam | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.jam b/build.jam index 4c389f0d..55f92415 100644 --- a/build.jam +++ b/build.jam @@ -3,9 +3,7 @@ # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) -require-b2 5.1 ; - -import project ; +require-b2 5.2 ; project /boost/cobalt : common-requirements From d6c0324f9eadf15e5c430a135bfe68512909f29c Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Tue, 23 Jul 2024 22:34:24 -0500 Subject: [PATCH 22/23] Move inter-lib dependencies to a project variable and into the build targets. --- build.jam | 33 ++++++++++++++++++--------------- build/Jamfile | 1 + 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/build.jam b/build.jam index 55f92415..d40de892 100644 --- a/build.jam +++ b/build.jam @@ -5,23 +5,25 @@ require-b2 5.2 ; +constant boost_dependencies : + /boost/asio//boost_asio + /boost/callable_traits//boost_callable_traits + /boost/circular_buffer//boost_circular_buffer + /boost/config//boost_config + /boost/container//boost_container + /boost/context//boost_context + /boost/core//boost_core + /boost/intrusive//boost_intrusive + /boost/leaf//boost_leaf + /boost/mp11//boost_mp11 + /boost/preprocessor//boost_preprocessor + /boost/smart_ptr//boost_smart_ptr + /boost/system//boost_system + /boost/throw_exception//boost_throw_exception + /boost/variant2//boost_variant2 ; + project /boost/cobalt : common-requirements - /boost/asio//boost_asio - /boost/callable_traits//boost_callable_traits - /boost/circular_buffer//boost_circular_buffer - /boost/config//boost_config - /boost/container//boost_container - /boost/context//boost_context - /boost/core//boost_core - /boost/intrusive//boost_intrusive - /boost/leaf//boost_leaf - /boost/mp11//boost_mp11 - /boost/preprocessor//boost_preprocessor - /boost/smart_ptr//boost_smart_ptr - /boost/system//boost_system - /boost/throw_exception//boost_throw_exception - /boost/variant2//boost_variant2 include ; @@ -32,3 +34,4 @@ explicit call-if : boost-library cobalt ; + diff --git a/build/Jamfile b/build/Jamfile index 523e919a..2810b731 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -18,6 +18,7 @@ project : requirements windows:WIN32_LEAN_AND_MEAN linux:-lpthread : source-location ../src + : common-requirements $(boost_dependencies) ; From c0b12faa6047be0a8cd2224bb20b50fb45498bd6 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Wed, 14 Aug 2024 22:46:21 -0500 Subject: [PATCH 23/23] Move custom features to importable jam. --- boost-cobalt.jam | 19 +++++++++++++++++++ build/Jamfile | 14 ++------------ example/Jamfile | 2 ++ test/Jamfile.jam | 2 ++ 4 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 boost-cobalt.jam diff --git a/boost-cobalt.jam b/boost-cobalt.jam new file mode 100644 index 00000000..6b2785d8 --- /dev/null +++ b/boost-cobalt.jam @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Klemens D. Morgenstern +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +import feature ; + + +feature.feature boost.cobalt.pmr : std boost-container custom no : propagated composite ; +feature.compose std : BOOST_COBALT_USE_STD_PMR=1 ; +feature.compose boost-container : BOOST_COBALT_USE_BOOST_CONTAINER_PMR=1 ; +feature.compose custom : BOOST_COBALT_USE_CUSTOM_PMR=1 ; +feature.compose no : BOOST_COBALT_NO_PMR=1 ; + +feature.feature boost.cobalt.executor : any_io_executor use_io_context custom : propagated composite ; +feature.compose any_io_executor : ; +feature.compose use_io_context : BOOST_COBALT_USE_IO_CONTEXT=1 ; +feature.compose custom_executor : BOOST_COBALT_CUSTOM_EXECUTOR=1 ; diff --git a/build/Jamfile b/build/Jamfile index 2810b731..c2b547d0 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -5,9 +5,10 @@ import os ; -import feature ; import-search /boost/config/checks ; import config : requires ; +import-search /boost/cobalt ; +import boost-cobalt ; project : requirements @@ -22,17 +23,6 @@ project : requirements ; -feature.feature boost.cobalt.pmr : std boost-container custom no : propagated composite ; -feature.compose std : BOOST_COBALT_USE_STD_PMR=1 ; -feature.compose boost-container : BOOST_COBALT_USE_BOOST_CONTAINER_PMR=1 ; -feature.compose custom : BOOST_COBALT_USE_CUSTOM_PMR=1 ; -feature.compose no : BOOST_COBALT_NO_PMR=1 ; - -feature.feature boost.cobalt.executor : any_io_executor use_io_context custom : propagated composite ; -feature.compose any_io_executor : ; -feature.compose use_io_context : BOOST_COBALT_USE_IO_CONTEXT=1 ; -feature.compose custom_executor : BOOST_COBALT_CUSTOM_EXECUTOR=1 ; - local config-binding = [ modules.binding config ] ; config-binding ?= "" ; diff --git a/example/Jamfile b/example/Jamfile index ec019ebd..dcf05328 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -5,6 +5,8 @@ import os ; +import-search /boost/cobalt ; +import boost-cobalt ; project : requirements diff --git a/test/Jamfile.jam b/test/Jamfile.jam index bf6e1199..25b1e7de 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -5,6 +5,8 @@ import os ; +import-search /boost/cobalt ; +import boost-cobalt ; project : requirements