diff --git a/.drone.star b/.drone.star index 19cb87f6..9537552c 100644 --- a/.drone.star +++ b/.drone.star @@ -36,6 +36,7 @@ deps = [ 'libs/mp11', 'libs/numeric', 'libs/optional', + 'libs/pool', 'libs/predef', 'libs/preprocessor', 'libs/range', 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.jam b/build.jam new file mode 100644 index 00000000..d40de892 --- /dev/null +++ b/build.jam @@ -0,0 +1,37 @@ +# 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) + +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 + 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..c2b547d0 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -5,8 +5,10 @@ import os ; -import feature ; -import ../../config/checks/config : requires ; +import-search /boost/config/checks ; +import config : requires ; +import-search /boost/cobalt ; +import boost-cobalt ; project : requirements @@ -17,20 +19,10 @@ project : requirements windows:WIN32_LEAN_AND_MEAN linux:-lpthread : source-location ../src + : common-requirements $(boost_dependencies) ; -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 ?= "" ; @@ -50,10 +42,10 @@ 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:/boost/container//boost_container [ check-target-builds $(config-binding:D)//cpp_lib_memory_resource cpp_lib_memory_resource @@ -62,8 +54,9 @@ lib boost_cobalt ] : usage-requirements - boost-container:/boost//container + 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 @@ -97,6 +90,3 @@ rule set-pmr-std ( props * ) return std ; } } - -boost-install boost_cobalt ; - diff --git a/doc/Jamfile b/doc/Jamfile index 19564375..7bd9dfce 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -14,6 +14,19 @@ explicit cobalt.pdf ; install pdf_ : cobalt.pdf : pdf ; explicit pdf_ ; +install images + : + images/awaitables.png + images/generators1.png + images/generators2.png + images/lazy_eager1.png + images/lazy_eager2.png + images/stackless1.png + images/stackless2.png + : + html/images + ; + alias boostdoc ; explicit boostdoc ; alias boostrelease : html_ ; diff --git a/doc/background/lazy_eager.adoc b/doc/background/lazy_eager.adoc index 889d37c9..569fa6a2 100644 --- a/doc/background/lazy_eager.adoc +++ b/doc/background/lazy_eager.adoc @@ -57,7 +57,7 @@ sequenceDiagram endif::[] ifndef::generate-diagram[] -image::{docdir}/images/lazy_eager1.png[] +image::lazy_eager1.png[] endif::[] @@ -113,5 +113,5 @@ sequenceDiagram endif::[] ifndef::generate-diagram[] -image::{docdir}/images/lazy_eager2.png[] +image::lazy_eager2.png[] endif::[] diff --git a/doc/background/stackless.adoc b/doc/background/stackless.adoc index e22c44ac..ac6e9036 100644 --- a/doc/background/stackless.adoc +++ b/doc/background/stackless.adoc @@ -42,7 +42,7 @@ sequenceDiagram endif::[] ifndef::generate-diagram[] -image::{docdir}/images/stackless1.png[] +image::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. @@ -100,7 +100,7 @@ sequenceDiagram endif::[] ifndef::generate-diagram[] -image::{docdir}/images/stackless2.png[] +image::stackless2.png[] endif::[] The same applies if a coroutine gets moved accross threads. \ No newline at end of file diff --git a/doc/index.adoc b/doc/index.adoc index e00a1676..56600034 100644 --- a/doc/index.adoc +++ b/doc/index.adoc @@ -11,7 +11,7 @@ Version 0.1, 29.01.2023 :source-language: c++ :example-caption: Example -:imagesdir: {docdir}/images +:imagesdir: ./images :leveloffset: +1 diff --git a/doc/overview.adoc b/doc/overview.adoc index e89fd97e..c423d281 100644 --- a/doc/overview.adoc +++ b/doc/overview.adoc @@ -68,7 +68,7 @@ Here's a list of relevant features in cobalt: |API reference |Look up details while coding -|<> +|<> |Some implementation details |Read if you're not confused enough diff --git a/doc/primer/awaitables.adoc b/doc/primer/awaitables.adoc index e5635aa6..2e175002 100644 --- a/doc/primer/awaitables.adoc +++ b/doc/primer/awaitables.adoc @@ -31,7 +31,7 @@ flowchart TD endif::[] ifndef::generate-diagram[] -image::{docdir}/images/awaitables.png[] +image::awaitables.png[] endif::[] In a `co_await` expression the waiting coroutine will first invoke diff --git a/doc/reference/generators.adoc b/doc/reference/generators.adoc index 88eb7fc7..d77214f3 100644 --- a/doc/reference/generators.adoc +++ b/doc/reference/generators.adoc @@ -52,7 +52,7 @@ sequenceDiagram endif::[] ifndef::generate-diagram[] -image::{docdir}/images/generators1.png[] +image::generators1.png[] endif::[] Values can be pushed into the generator, when `Push` (the second template parameter) is set to non-void: @@ -142,7 +142,7 @@ sequenceDiagram endif::[] ifndef::generate-diagram[] -image::{docdir}/images/generators2.png[] +image::generators2.png[] endif::[] [#generator-executor] diff --git a/doc/tour/promise.adoc b/doc/tour/promise.adoc index e85d776f..3fa9c3b7 100644 --- a/doc/tour/promise.adoc +++ b/doc/tour/promise.adoc @@ -18,7 +18,7 @@ cobalt::main co_main(int argc, char * argv[]) // do something else here co_await do_the_other_thing(); // wait for the promise to complete - auto res = co_wait p; + auto res = co_await p; co_return res; } diff --git a/doc/tour/task.adoc b/doc/tour/task.adoc index 3ccf3449..dd1864f3 100644 --- a/doc/tour/task.adoc +++ b/doc/tour/task.adoc @@ -17,7 +17,7 @@ cobalt::main co_main(int argc, char * argv[]) // do something else here first co_await do_the_other_thing(); // start and wait for the task to complete - auto res = co_wait t; + auto res = co_await t; co_return res; } ---- diff --git a/example/Jamfile b/example/Jamfile index dbfa9768..dcf05328 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -5,6 +5,8 @@ import os ; +import-search /boost/cobalt ; +import boost-cobalt ; project : requirements @@ -18,12 +20,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/include/boost/cobalt/channel.hpp b/include/boost/cobalt/channel.hpp index 98e71176..e59c6dc9 100644 --- a/include/boost/cobalt/channel.hpp +++ b/include/boost/cobalt/channel.hpp @@ -85,6 +85,13 @@ struct channel this->unlink(); } + void interrupt_await() + { + this->cancelled = true; + if (awaited_from) + awaited_from.release().resume(); + } + struct cancel_impl; bool await_ready() { return !chn->buffer_.empty(); } template @@ -118,6 +125,13 @@ struct channel this->unlink(); } + void interrupt_await() + { + this->cancelled = true; + if (awaited_from) + awaited_from.release().resume(); + } + struct cancel_impl; bool await_ready() { return !chn->buffer_.full(); } @@ -234,6 +248,13 @@ struct channel this->unlink(); } + void interrupt_await() + { + this->cancelled = true; + if (awaited_from) + awaited_from.release().resume(); + } + struct cancel_impl; bool await_ready() { return (chn->n_ > 0); } template @@ -261,6 +282,13 @@ struct channel this->unlink(); } + void interrupt_await() + { + this->cancelled = true; + if (awaited_from) + awaited_from.release().resume(); + } + struct cancel_impl; bool await_ready() { diff --git a/include/boost/cobalt/detail/fork.hpp b/include/boost/cobalt/detail/fork.hpp index 230ee049..6f4735bd 100644 --- a/include/boost/cobalt/detail/fork.hpp +++ b/include/boost/cobalt/detail/fork.hpp @@ -18,6 +18,7 @@ #include #include +#include namespace boost::cobalt::detail { @@ -56,13 +57,13 @@ struct fork bool outstanding_work() {return use_count != 0u;} - const executor * exec = nullptr; - bool wired_up() {return exec != nullptr;} + std::optional exec; + bool wired_up() {return exec.has_value();} using executor_type = executor; const executor_type & get_executor() const { - BOOST_ASSERT(exec != nullptr); + BOOST_ASSERT(exec.has_value()); return *exec; } diff --git a/include/boost/cobalt/detail/gather.hpp b/include/boost/cobalt/detail/gather.hpp index 171be8e1..32521556 100644 --- a/include/boost/cobalt/detail/gather.hpp +++ b/include/boost/cobalt/detail/gather.hpp @@ -163,7 +163,7 @@ struct gather_variadic_impl #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) this->loc = loc; #endif - this->exec = &cobalt::detail::get_executor(h); + this->exec = cobalt::detail::get_executor(h); last_forked.release().resume(); while (last_index < tuple_size) impls[last_index++](*this).release(); @@ -362,7 +362,7 @@ struct gather_ranged_impl #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) this->loc = loc; #endif - exec = &detail::get_executor(h); + exec = detail::get_executor(h); last_forked.release().resume(); while (last_index < cancel.size()) diff --git a/include/boost/cobalt/detail/handler.hpp b/include/boost/cobalt/detail/handler.hpp index 72d9b510..8376c6b1 100644 --- a/include/boost/cobalt/detail/handler.hpp +++ b/include/boost/cobalt/detail/handler.hpp @@ -23,6 +23,8 @@ namespace boost::cobalt namespace detail { +template +struct composition_promise; enum class completed_immediately_t { no, maybe, yes, initiating @@ -73,6 +75,24 @@ struct completion_handler_noop_executor }; +template +executor +get_executor(std::coroutine_handle h) +{ + if constexpr (requires {h.promise().get_executor();}) + { + return h.promise().get_executor(); + } + else + return this_thread::get_executor(); +} + +inline executor +get_executor(std::coroutine_handle<>) +{ + return this_thread::get_executor(); +} + struct completion_handler_base { @@ -84,7 +104,7 @@ struct completion_handler_base } using executor_type = executor; - const executor_type & executor_ ; + executor_type executor_ ; const executor_type & get_executor() const noexcept { return executor_ ; @@ -113,11 +133,10 @@ struct completion_handler_base } template - requires (requires (Promise p) {{p.get_executor()} -> std::same_as;}) completion_handler_base(std::coroutine_handle h, completed_immediately_t * completed_immediately = nullptr) : cancellation_slot(asio::get_associated_cancellation_slot(h.promise())), - executor_(h.promise().get_executor()), + executor_(cobalt::detail::get_executor(h)), #if !defined(BOOST_COBALT_NO_PMR) allocator(asio::get_associated_allocator(h.promise(), this_thread::get_allocator())), #else @@ -128,24 +147,22 @@ struct completion_handler_base } #if !defined(BOOST_COBALT_NO_PMR) template - requires (requires (Promise p) {{p.get_executor()} -> std::same_as;}) completion_handler_base(std::coroutine_handle h, pmr::memory_resource * resource, completed_immediately_t * completed_immediately = nullptr) : cancellation_slot(asio::get_associated_cancellation_slot(h.promise())), - executor_(h.promise().get_executor()), + executor_(cobalt::detail::get_executor(h)), allocator(resource), completed_immediately(completed_immediately) { } #else template - requires (requires (Promise p) {{p.get_executor()} -> std::same_as;}) completion_handler_base(std::coroutine_handle h, detail::sbo_resource * resource, completed_immediately_t * completed_immediately = nullptr) : cancellation_slot(asio::get_associated_cancellation_slot(h.promise())), - executor_(h.promise().get_executor()), + executor_(cobalt::detail::get_executor(h)), allocator(resource), completed_immediately(completed_immediately) { @@ -166,27 +183,6 @@ void assign_cancellation(std::coroutine_handle h, Handler && func) h.promise().get_cancellation_slot().assign(std::forward(func)); } -template -const executor & -get_executor(std::coroutine_handle h) -{ - if constexpr (requires {h.promise().get_executor();}) - { - static_assert(std::same_as, - "for performance reasons, the get_executor function on a promise must return a const reference"); - return h.promise().get_executor(); - } - else - return this_thread::get_executor(); -} - -inline const executor & -get_executor(std::coroutine_handle<>) -{ - return this_thread::get_executor(); -} - } template @@ -285,6 +281,9 @@ struct completion_handler : detail::completion_handler_base #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) boost::source_location loc_; #endif + + template + friend struct detail::composition_promise; }; }; diff --git a/include/boost/cobalt/detail/join.hpp b/include/boost/cobalt/detail/join.hpp index 221ab53e..7fb1e68f 100644 --- a/include/boost/cobalt/detail/join.hpp +++ b/include/boost/cobalt/detail/join.hpp @@ -192,7 +192,7 @@ struct join_variadic_impl #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) this->loc = loc; #endif - this->exec = &detail::get_executor(h); + this->exec = detail::get_executor(h); last_forked.release().resume(); while (last_index < tuple_size) impls[last_index++](*this).release(); @@ -450,7 +450,7 @@ struct join_ranged_impl #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) this->loc = loc; #endif - exec = &detail::get_executor(h); + exec = detail::get_executor(h); last_forked.release().resume(); while (last_index < cancel.size()) diff --git a/include/boost/cobalt/detail/race.hpp b/include/boost/cobalt/detail/race.hpp index 90f6b824..36854c4b 100644 --- a/include/boost/cobalt/detail/race.hpp +++ b/include/boost/cobalt/detail/race.hpp @@ -101,9 +101,7 @@ struct race_variadic_impl struct awaitable : fork::static_shared_state<256 * tuple_size> { -#if !defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) boost::source_location loc; -#endif template awaitable(std::tuple & args, URBG & g, std::index_sequence) : @@ -307,7 +305,7 @@ struct race_variadic_impl { this->loc = loc; - this->exec = &cobalt::detail::get_executor(h); + this->exec = cobalt::detail::get_executor(h); last_forked.release().resume(); if (!this->outstanding_work()) // already done, resume rightaway. @@ -619,7 +617,7 @@ struct race_ranged_impl const boost::source_location & loc = BOOST_CURRENT_LOCATION) { this->loc = loc; - this->exec = &detail::get_executor(h); + this->exec = detail::get_executor(h); last_forked.release().resume(); if (!this->outstanding_work()) // already done, resume rightaway. diff --git a/include/boost/cobalt/detail/sbo_resource.hpp b/include/boost/cobalt/detail/sbo_resource.hpp index 2b72efea..ae37315e 100644 --- a/include/boost/cobalt/detail/sbo_resource.hpp +++ b/include/boost/cobalt/detail/sbo_resource.hpp @@ -136,9 +136,24 @@ struct sbo_resource operator delete(p, std::align_val_t(align)); #endif #endif + } } +#if defined(BOOST_COBALT_NO_PMR) + [[nodiscard]] + void* + allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) + { + return ::operator new(bytes, do_allocate(bytes, alignment)); + } + + void + deallocate(void* p, size_t bytes, size_t alignment = alignof(std::max_align_t)) + { + return do_deallocate(p, bytes, alignment); + } +#endif #if !defined(BOOST_COBALT_NO_PMR) constexpr bool do_is_equal(memory_resource const& other) const noexcept override @@ -179,6 +194,8 @@ struct sbo_allocator resource_->do_deallocate(p, sizeof(T) * n, alignof(T)); } sbo_allocator(sbo_resource * resource) : resource_(resource) {} + + sbo_resource * resource() const {return resource_;} private: template friend struct sbo_allocator; diff --git a/include/boost/cobalt/detail/spawn.hpp b/include/boost/cobalt/detail/spawn.hpp index 296a5769..2b80f16f 100644 --- a/include/boost/cobalt/detail/spawn.hpp +++ b/include/boost/cobalt/detail/spawn.hpp @@ -74,9 +74,12 @@ struct async_initiate_spawn decltype(recs) r; Handler handler; + asio::cancellation_slot sl; void operator()() { + if (sl.is_connected()) + sl.clear(); auto ex = r->exception; T rr{}; if (r->result) @@ -88,7 +91,7 @@ struct async_initiate_spawn p->awaited_from.reset(detail::post_coroutine( completion_handler{ - alloc, asio::get_associated_executor(h, exec), std::move(recs), std::move(h) + alloc, asio::get_associated_executor(h, exec), std::move(recs), std::move(h), sl }).address()); asio::dispatch(exec, std::coroutine_handle>::from_promise(*p->promise)); diff --git a/include/boost/cobalt/experimental/composition.hpp b/include/boost/cobalt/experimental/composition.hpp new file mode 100644 index 00000000..d916409b --- /dev/null +++ b/include/boost/cobalt/experimental/composition.hpp @@ -0,0 +1,238 @@ +// +// 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_COMPOSITION_HPP +#define BOOST_COBALT_EXPERIMENTAL_COMPOSITION_HPP + +#include +#include + + +namespace boost::cobalt::detail +{ + +template +struct composition_promise + : + promise_cancellation_base, + enable_await_allocator>, + enable_await_executor> +{ + void get_return_object() {} + + using promise_cancellation_base::await_transform; + using enable_await_allocator>::await_transform; + using enable_await_executor>::await_transform; + + using handler_type = completion_handler; + + using allocator_type = typename handler_type::allocator_type; + allocator_type get_allocator() const {return handler.get_allocator();} +#if !defined(BOOST_COBALT_NO_PMR) + using resource_type = pmr::memory_resource; +#else + using resource_type = cobalt::detail::sbo_resource; +#endif + + template + BOOST_NOINLINE + auto await_transform(asio::deferred_async_operation op_) + { + struct deferred_op : op + { + asio::deferred_async_operation op_; + deferred_op(asio::deferred_async_operation op_, + resource_type * resource) + : op_(std::move(op_)), resource(resource) {} + + void initiate(cobalt::completion_handler complete) override + { + std::move(op_)(std::move(complete)); + } + + resource_type * resource; + + typename op::awaitable_base operator co_await() + { + return static_cast&&>(*this).operator co_await().replace_resource(this->resource); + } + }; + + return cobalt::as_tuple(deferred_op{std::move(op_), handler.get_allocator().resource()}); + } + + template + requires requires (Op && op, resource_type* res) + { + {static_cast(op).operator co_await().replace_resource(res)} -> awaitable_type; + } + BOOST_NOINLINE + auto await_transform(Op && op_) + { + struct replacing_op + { + Op op; + + resource_type * resource; + + auto operator co_await() + { + return std::forward(op).operator co_await().replace_resource(this->resource); + } + }; + + return cobalt::as_tuple(replacing_op{std::forward(op_), handler.get_allocator().resource()}); + } + + + using executor_type = typename handler_type::executor_type ; + const executor & get_executor() const {return handler.get_executor();} + + template + static void * operator new(const std::size_t size, Ts & ... 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 = std::get(std::tie(args...)).get_allocator().resource(); + 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))); + } + + completion_handler handler; + + template + composition_promise(Ts && ... args) : handler(std::move(std::get(std::tie(args...)))) + { + + } + + void unhandled_exception() { throw ; } + constexpr static std::suspend_never initial_suspend() {return {};} + + void return_value(std::tuple args) + { + handler.result.emplace(std::move(args)); + } + + + struct final_awaitable + { + constexpr bool await_ready() noexcept {return false;} + completion_handler handler; + + BOOST_NOINLINE + std::coroutine_handle await_suspend(std::coroutine_handle h) noexcept + { + auto exec = handler.get_executor(); + auto ho = handler.self.release(); + detail::self_destroy(h, exec); + return ho; + } + + constexpr void await_resume() noexcept {} + }; + + BOOST_NOINLINE + auto final_suspend() noexcept + { + return final_awaitable{std::move(handler)}; + } +}; + +} + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + +#endif //BOOST_COBALT_EXPERIMENTAL_COMPOSITION_HPP diff --git a/include/boost/cobalt/op.hpp b/include/boost/cobalt/op.hpp index a2e16958..edc24fa6 100644 --- a/include/boost/cobalt/op.hpp +++ b/include/boost/cobalt/op.hpp @@ -27,17 +27,23 @@ struct op virtual void initiate(cobalt::completion_handler complete) = 0 ; virtual ~op() = default; - struct awaitable + struct awaitable_base { op &op_; std::optional> result; - awaitable(op * op_) : op_(*op_) {} - awaitable(awaitable && lhs) - : op_(lhs.op_) - , result(std::move(lhs.result)) - { - } +#if !defined(BOOST_COBALT_NO_PMR) + using resource_type = pmr::memory_resource; +#else + using resource_type = detail::sbo_resource; +#endif + + awaitable_base(op * op_, resource_type *resource) : op_(*op_), resource(resource) {} + awaitable_base(awaitable_base && lhs) noexcept = default; + +#if defined(_MSC_VER) + BOOST_NOINLINE ~awaitable_base() {} +#endif bool await_ready() { @@ -45,12 +51,11 @@ struct op return result.has_value(); } - char buffer[BOOST_COBALT_SBO_BUFFER_SIZE]; - detail::sbo_resource resource{buffer, sizeof(buffer)}; - detail::completed_immediately_t completed_immediately = detail::completed_immediately_t::no; std::exception_ptr init_ep; + resource_type *resource; + template bool await_suspend(std::coroutine_handle h #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) @@ -63,9 +68,9 @@ struct op completed_immediately = detail::completed_immediately_t::initiating; #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) - op_.initiate(completion_handler{h, result, &resource, &completed_immediately, loc}); + op_.initiate(completion_handler{h, result, resource, &completed_immediately, loc}); #else - op_.initiate(completion_handler{h, result, &resource, &completed_immediately}); + op_.initiate(completion_handler{h, result, resource, &completed_immediately}); #endif if (completed_immediately == detail::completed_immediately_t::initiating) completed_immediately = detail::completed_immediately_t::no; @@ -86,6 +91,9 @@ struct op return await_resume(as_result_tag{}).value(loc); } +#if defined(_MSC_VER) + BOOST_NOINLINE +#endif auto await_resume(const struct as_tuple_tag &) { if (init_ep) @@ -93,6 +101,9 @@ struct op return *std::move(result); } +#if defined(_MSC_VER) + BOOST_NOINLINE +#endif auto await_resume(const struct as_result_tag &) { if (init_ep) @@ -101,6 +112,25 @@ struct op } }; + struct awaitable : awaitable_base + { + char buffer[BOOST_COBALT_SBO_BUFFER_SIZE]; + detail::sbo_resource resource{buffer, sizeof(buffer)}; + + awaitable(op * op_) : awaitable_base(op_, &resource) {} + awaitable(awaitable && rhs) : awaitable_base(std::move(rhs)) + { + this->awaitable_base::resource = &resource; + } + + awaitable_base replace_resource(typename awaitable_base::resource_type * resource) && + { + awaitable_base nw = std::move(*this); + nw.resource = resource; + return nw; + } + }; + awaitable operator co_await() && { return awaitable{this}; diff --git a/include/boost/cobalt/result.hpp b/include/boost/cobalt/result.hpp index 5aa15ac8..a5e19b79 100644 --- a/include/boost/cobalt/result.hpp +++ b/include/boost/cobalt/result.hpp @@ -67,7 +67,7 @@ auto interpret_as_result(std::tuple && args) -> system::result(args))}; return { system::in_place_value, - std::apply([](auto, auto && ... rest) {return std::make_tuple(std::move(rest)...);}) + std::apply([](auto, auto && ... rest) {return std::make_tuple(std::move(rest)...);}, std::move(args)) }; } diff --git a/src/detail/util.cpp b/src/detail/util.cpp index 3d0202af..d2451462 100644 --- a/src/detail/util.cpp +++ b/src/detail/util.cpp @@ -18,12 +18,17 @@ namespace boost::cobalt::detail void self_destroy(std::coroutine_handle h, const cobalt::executor & exec) noexcept { +#if defined(BOOST_COBALT_NO_PMR) + asio::post(exec, [del=unique_handle(h.address())]() mutable {}); +#else asio::post(exec, asio::bind_allocator( this_thread::get_allocator(), [del=unique_handle(h.address())]() mutable { })); +#endif + } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c86699ce..132f9df5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,7 +20,7 @@ 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) -add_executable(boost_cobalt_experimental EXCLUDE_FROM_ALL 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 experimental/composition.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/Jamfile.jam b/test/Jamfile.jam index 696f22b7..596927bf 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -5,6 +5,8 @@ import os ; +import-search /boost/cobalt ; +import boost-cobalt ; project : requirements @@ -20,16 +22,16 @@ 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 ] { run $(src) test_impl ; } -run experimental/context.cpp test_impl //boost/context ; \ No newline at end of file +run experimental/context.cpp experimental/composition.cpp test_impl //boost/context ; \ No newline at end of file diff --git a/test/experimental/composition.cpp b/test/experimental/composition.cpp new file mode 100644 index 00000000..b7291cad --- /dev/null +++ b/test/experimental/composition.cpp @@ -0,0 +1,30 @@ +// +// 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) +// + +#include + +#include "../test.hpp" + +using namespace boost::cobalt; + +struct dummy_op final : op +{ + void initiate(completion_handler) + { + std::tuple<> t = co_await boost::asio::post(co_await this_coro::executor, boost::asio::deferred); + t = co_await boost::asio::post(co_await this_coro::executor, use_op); + co_return {boost::asio::error::no_such_device, 42}; + } +}; + +CO_TEST_CASE(composition) +{ + auto [ec, n] = co_await boost::cobalt::as_tuple(dummy_op{}); + BOOST_CHECK(ec == boost::asio::error::no_such_device); + BOOST_CHECK(n == 42); + co_return ; +} diff --git a/test/experimental/yield_context.cpp b/test/experimental/yield_context.cpp index 2c4919c2..2b930316 100644 --- a/test/experimental/yield_context.cpp +++ b/test/experimental/yield_context.cpp @@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(await) boost::asio::io_context ioc; boost::asio::spawn(ioc, - [&](boost::asio::yield_context ctx) + [&](boost::asio::basic_yield_context ctx) { experimental::await(dummy_aw(), ctx); experimental::await(t(), ctx); diff --git a/test/op.cpp b/test/op.cpp index ea96bbdd..55477b12 100644 --- a/test/op.cpp +++ b/test/op.cpp @@ -209,5 +209,30 @@ CO_TEST_CASE(deferred) std::tuple r = co_await tim.async_wait(asio::as_tuple); } +template +auto test_multi_values(int a, int b, Token && token) +{ + return asio::async_initiate( + [](auto h, int a, int b) + { + asio::dispatch( + asio::get_associated_immediate_executor( + h, asio::get_associated_executor(h)), + [h = std::move(h), a, b]() mutable { + std::move(h)({}, a, b); + } + ); + }, + std::forward(token), + a, b + ); +} + +CO_TEST_CASE(multi_values) +{ + auto [a, b] = co_await test_multi_values(12, 37, cobalt::use_op); + BOOST_CHECK_EQUAL(a, 12); + BOOST_CHECK_EQUAL(b, 37); +} -BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END();