├── .drone.star ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── bench ├── CMakeLists.txt ├── channel.cpp ├── immediate.cpp ├── monotonic.cpp ├── parallel.cpp └── post.cpp ├── boost-cobalt.jam ├── build.jam ├── build └── Jamfile ├── cmake ├── CheckRequirements.cmake ├── concepts.cpp ├── coroutine.cpp └── memory_resource.cpp ├── doc ├── Jamfile ├── acknowledgements.adoc ├── background │ ├── asio_awaitable.adoc │ ├── custom_executors.adoc │ ├── lazy_eager.adoc │ └── stackless.adoc ├── benchmarks.adoc ├── compiler.adoc ├── design │ ├── associators.adoc │ ├── concepts.adoc │ ├── promise.adoc │ ├── race.adoc │ ├── thread.adoc │ └── thread_local.adoc ├── images │ ├── awaitables.png │ ├── generators1.png │ ├── generators2.png │ ├── lazy_eager1.png │ ├── lazy_eager2.png │ ├── stackless1.png │ └── stackless2.png ├── index.adoc ├── motivation.adoc ├── overview.adoc ├── primer │ ├── async.adoc │ ├── awaitables.adoc │ ├── coroutines.adoc │ └── event-loops.adoc ├── reference │ ├── async_for.adoc │ ├── channel.adoc │ ├── concepts.adoc │ ├── config.adoc │ ├── detached.adoc │ ├── error.adoc │ ├── experimental │ │ └── context.adoc │ ├── gather.adoc │ ├── generators.adoc │ ├── join.adoc │ ├── main.adoc │ ├── op.adoc │ ├── promise.adoc │ ├── race.adoc │ ├── result.adoc │ ├── run.adoc │ ├── spawn.adoc │ ├── task.adoc │ ├── this_coro.adoc │ ├── this_thread.adoc │ ├── thread.adoc │ ├── wait_group.adoc │ └── with.adoc ├── tour │ ├── entry.adoc │ ├── generator.adoc │ ├── join.adoc │ ├── promise.adoc │ ├── race.adoc │ └── task.adoc └── tutorial │ ├── advanced.adoc │ ├── delay.adoc │ ├── delay_op.adoc │ ├── echo_server.adoc │ ├── push_generator.adoc │ └── ticker.adoc ├── example ├── CMakeLists.txt ├── Jamfile ├── channel.cpp ├── delay.cpp ├── delay_op.cpp ├── echo_server.cpp ├── http.cpp ├── outcome.cpp ├── python.cpp ├── python.py ├── signals.cpp ├── spsc.cpp ├── thread.cpp ├── thread_pool.cpp └── ticker.cpp ├── include └── boost │ ├── cobalt.hpp │ └── cobalt │ ├── async_for.hpp │ ├── channel.hpp │ ├── concepts.hpp │ ├── config.hpp │ ├── detached.hpp │ ├── detail │ ├── await_result_helper.hpp │ ├── detached.hpp │ ├── exception.hpp │ ├── fork.hpp │ ├── forward_cancellation.hpp │ ├── gather.hpp │ ├── generator.hpp │ ├── handler.hpp │ ├── join.hpp │ ├── main.hpp │ ├── monotonic_resource.hpp │ ├── promise.hpp │ ├── race.hpp │ ├── sbo_resource.hpp │ ├── spawn.hpp │ ├── task.hpp │ ├── this_thread.hpp │ ├── thread.hpp │ ├── util.hpp │ ├── wait_group.hpp │ ├── with.hpp │ └── wrapper.hpp │ ├── error.hpp │ ├── experimental │ ├── composition.hpp │ ├── context.hpp │ ├── frame.hpp │ └── yield_context.hpp │ ├── gather.hpp │ ├── generator.hpp │ ├── impl │ └── channel.hpp │ ├── join.hpp │ ├── main.hpp │ ├── noop.hpp │ ├── op.hpp │ ├── promise.hpp │ ├── race.hpp │ ├── result.hpp │ ├── run.hpp │ ├── spawn.hpp │ ├── task.hpp │ ├── this_coro.hpp │ ├── this_thread.hpp │ ├── thread.hpp │ ├── unique_handle.hpp │ ├── wait_group.hpp │ └── with.hpp ├── index.html ├── meta └── libraries.json ├── readme.md ├── src ├── channel.cpp ├── detail │ ├── exception.cpp │ └── util.cpp ├── error.cpp ├── main.cpp ├── this_thread.cpp └── thread.cpp └── test ├── CMakeLists.txt ├── Jamfile.jam ├── any_completion_handler.cpp ├── async_for.cpp ├── channel.cpp ├── cmake_install_test ├── CMakeLists.txt └── main.cpp ├── cmake_subdir_test ├── CMakeLists.txt └── main.cpp ├── concepts.cpp ├── detached.cpp ├── experimental ├── composition.cpp ├── context.cpp └── yield_context.cpp ├── fork.cpp ├── gather.cpp ├── generator.cpp ├── handler.cpp ├── join.cpp ├── left_race.cpp ├── main.cpp ├── main_compile.cpp ├── monotonic_resource.cpp ├── op.cpp ├── promise.cpp ├── race.cpp ├── run.cpp ├── sbo_resource.cpp ├── strand.cpp ├── task.cpp ├── test.hpp ├── test_main.cpp ├── this_coro.cpp ├── thread.cpp ├── util.cpp ├── wait_group.cpp ├── with.cpp └── wrappers.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-* 2 | .idea 3 | doc/html/ 4 | -------------------------------------------------------------------------------- /bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_executable(boost_cobalt_post_bench post.cpp) 3 | target_link_libraries(boost_cobalt_post_bench PRIVATE Boost::cobalt Boost::system Threads::Threads) 4 | if (TARGET Boost::context) 5 | target_link_libraries(boost_cobalt_post_bench PRIVATE Boost::context) 6 | target_compile_definitions(boost_cobalt_post_bench PRIVATE BOOST_COBALT_BENCH_WITH_CONTEXT=1) 7 | set_property(TARGET boost_cobalt_post_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) 8 | endif() 9 | 10 | add_executable(boost_cobalt_immediate_bench immediate.cpp) 11 | target_link_libraries(boost_cobalt_immediate_bench PRIVATE Boost::cobalt Boost::system Threads::Threads) 12 | if (TARGET Boost::context) 13 | target_link_libraries(boost_cobalt_immediate_bench PRIVATE Boost::context) 14 | target_compile_definitions(boost_cobalt_immediate_bench PRIVATE BOOST_COBALT_BENCH_WITH_CONTEXT=1) 15 | set_property(TARGET boost_cobalt_immediate_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) 16 | endif() 17 | 18 | add_executable(boost_cobalt_channel_bench channel.cpp) 19 | target_link_libraries(boost_cobalt_channel_bench PRIVATE Boost::cobalt Boost::system Threads::Threads) 20 | if (TARGET Boost::context) 21 | target_link_libraries(boost_cobalt_channel_bench PRIVATE Boost::context) 22 | target_compile_definitions(boost_cobalt_channel_bench PRIVATE BOOST_COBALT_BENCH_WITH_CONTEXT=1) 23 | set_property(TARGET boost_cobalt_channel_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) 24 | endif() 25 | 26 | add_executable(boost_cobalt_parallel_bench parallel.cpp) 27 | target_link_libraries(boost_cobalt_parallel_bench PRIVATE Boost::cobalt Boost::system Threads::Threads) 28 | if (TARGET Boost::context) 29 | target_link_libraries(boost_cobalt_parallel_bench PRIVATE Boost::context) 30 | target_compile_definitions(boost_cobalt_parallel_bench PRIVATE BOOST_COBALT_BENCH_WITH_CONTEXT=1) 31 | set_property(TARGET boost_cobalt_parallel_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) 32 | endif() 33 | 34 | 35 | 36 | add_executable(boost_cobalt_monotonic_bench monotonic.cpp) 37 | target_link_libraries(boost_cobalt_monotonic_bench PRIVATE Boost::cobalt Boost::system Threads::Threads) 38 | if (TARGET Boost::context) 39 | target_link_libraries(boost_cobalt_monotonic_bench PRIVATE Boost::context) 40 | target_compile_definitions(boost_cobalt_monotonic_bench PRIVATE BOOST_COBALT_BENCH_WITH_CONTEXT=1) 41 | set_property(TARGET boost_cobalt_monotonic_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) 42 | endif() 43 | -------------------------------------------------------------------------------- /bench/channel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 13 | #include 14 | #endif 15 | 16 | using namespace boost; 17 | constexpr std::size_t n = 3'000'000ull; 18 | 19 | cobalt::task atest() 20 | { 21 | cobalt::channel chan{0u}; 22 | for (std::size_t i = 0u; i < n; i++) 23 | co_await cobalt::gather(chan.write(), chan.read()); 24 | 25 | } 26 | 27 | asio::awaitable awtest() 28 | { 29 | asio::experimental::channel chan{co_await cobalt::this_coro::executor, 0u}; 30 | for (std::size_t i = 0u; i < n; i++) 31 | co_await asio::experimental::make_parallel_group( 32 | chan.async_send(system::error_code{}, asio::deferred), 33 | chan.async_receive(asio::deferred)) 34 | .async_wait(asio::experimental::wait_for_all(), asio::deferred); 35 | } 36 | 37 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 38 | 39 | void stest(asio::yield_context ctx) 40 | { 41 | asio::experimental::channel chan{ctx.get_executor(), 0u}; 42 | for (std::size_t i = 0u; i < n; i++) 43 | asio::experimental::make_parallel_group( 44 | chan.async_send(system::error_code{}, asio::deferred), 45 | chan.async_receive(asio::deferred)) 46 | .async_wait(asio::experimental::wait_for_all(), ctx); 47 | } 48 | #endif 49 | 50 | 51 | int main(int argc, char * argv[]) 52 | { 53 | { 54 | auto start = std::chrono::steady_clock::now(); 55 | cobalt::run(atest()); 56 | auto end = std::chrono::steady_clock::now(); 57 | printf("cobalt : %ld ms\n", std::chrono::duration_cast(end - start).count()); 58 | } 59 | 60 | { 61 | auto start = std::chrono::steady_clock::now(); 62 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 63 | asio::co_spawn(ctx, awtest(), asio::detached); 64 | ctx.run(); 65 | auto end = std::chrono::steady_clock::now(); 66 | printf("awaitable: %ld ms\n", std::chrono::duration_cast(end - start).count()); 67 | } 68 | 69 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 70 | { 71 | auto start = std::chrono::steady_clock::now(); 72 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 73 | asio::spawn(ctx, stest, asio::detached); 74 | ctx.run(); 75 | auto end = std::chrono::steady_clock::now(); 76 | printf("stackful : %ld ms\n", std::chrono::duration_cast(end - start).count()); 77 | } 78 | #endif 79 | 80 | return 0; 81 | } -------------------------------------------------------------------------------- /bench/immediate.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 12 | #include 13 | #endif 14 | 15 | using namespace boost; 16 | constexpr std::size_t n = 10'000'000ull; 17 | 18 | cobalt::task atest() 19 | { 20 | asio::experimental::channel chan{co_await cobalt::this_coro::executor, 1u}; 21 | for (std::size_t i = 0u; i < n; i++) 22 | { 23 | co_await chan.async_send(system::error_code{}, cobalt::use_op); 24 | co_await chan.async_receive(cobalt::use_op); 25 | } 26 | 27 | } 28 | 29 | asio::awaitable awtest() 30 | { 31 | asio::experimental::channel chan{co_await cobalt::this_coro::executor, 1u}; 32 | for (std::size_t i = 0u; i < n; i++) 33 | { 34 | co_await chan.async_send(system::error_code{}, asio::deferred); 35 | co_await chan.async_receive(asio::deferred); 36 | } 37 | } 38 | 39 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 40 | 41 | void stest(asio::yield_context ctx) 42 | { 43 | asio::experimental::channel chan{ctx.get_executor(), 1u}; 44 | for (std::size_t i = 0u; i < n; i++) 45 | { 46 | chan.async_send(system::error_code{}, ctx); 47 | chan.async_receive(ctx); 48 | } 49 | } 50 | #endif 51 | 52 | 53 | int main(int argc, char * argv[]) 54 | { 55 | { 56 | auto start = std::chrono::steady_clock::now(); 57 | cobalt::run(atest()); 58 | auto end = std::chrono::steady_clock::now(); 59 | printf("cobalt : %ld ms\n", std::chrono::duration_cast(end - start).count()); 60 | } 61 | 62 | { 63 | auto start = std::chrono::steady_clock::now(); 64 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 65 | asio::co_spawn(ctx, awtest(), asio::detached); 66 | ctx.run(); 67 | auto end = std::chrono::steady_clock::now(); 68 | printf("awaitable: %ld ms\n", std::chrono::duration_cast(end - start).count()); 69 | } 70 | 71 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 72 | { 73 | auto start = std::chrono::steady_clock::now(); 74 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 75 | asio::spawn(ctx, stest, asio::detached); 76 | ctx.run(); 77 | auto end = std::chrono::steady_clock::now(); 78 | printf("stackful : %ld ms\n", std::chrono::duration_cast(end - start).count()); 79 | } 80 | #endif 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /bench/parallel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 14 | #include 15 | #endif 16 | 17 | using namespace boost; 18 | constexpr std::size_t n = 3'000'000ull; 19 | 20 | cobalt::task atest() 21 | { 22 | asio::experimental::channel chan{co_await cobalt::this_coro::executor, 0u}; 23 | for (std::size_t i = 0u; i < n; i++) 24 | co_await cobalt::gather( 25 | chan.async_send(system::error_code{}, cobalt::use_task), 26 | chan.async_receive(cobalt::use_task)); 27 | 28 | } 29 | 30 | asio::awaitable awtest() 31 | { 32 | asio::experimental::channel chan{co_await cobalt::this_coro::executor, 0u}; 33 | using boost::asio::experimental::awaitable_operators::operator&&; 34 | for (std::size_t i = 0u; i < n; i++) 35 | co_await ( 36 | chan.async_send(system::error_code{}, asio::use_awaitable) 37 | && 38 | chan.async_receive(asio::use_awaitable)); 39 | } 40 | 41 | int main(int argc, char * argv[]) 42 | { 43 | { 44 | auto start = std::chrono::steady_clock::now(); 45 | cobalt::run(atest()); 46 | auto end = std::chrono::steady_clock::now(); 47 | printf("cobalt : %ld ms\n", std::chrono::duration_cast(end - start).count()); 48 | } 49 | 50 | { 51 | auto start = std::chrono::steady_clock::now(); 52 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 53 | asio::co_spawn(ctx, awtest(), asio::detached); 54 | ctx.run(); 55 | auto end = std::chrono::steady_clock::now(); 56 | printf("awaitable: %ld ms\n", std::chrono::duration_cast(end - start).count()); 57 | } 58 | 59 | return 0; 60 | } -------------------------------------------------------------------------------- /bench/post.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | #include 8 | #include 9 | 10 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 11 | #include 12 | #endif 13 | 14 | using namespace boost; 15 | constexpr std::size_t n = 50'000'000ull; 16 | 17 | cobalt::task atest() 18 | { 19 | for (std::size_t i = 0u; i < n; i++) 20 | co_await asio::post(cobalt::use_op); 21 | } 22 | 23 | asio::awaitable awtest() 24 | { 25 | for (std::size_t i = 0u; i < n; i++) 26 | co_await asio::post(asio::deferred); 27 | } 28 | 29 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 30 | 31 | void stest(asio::yield_context ctx) 32 | { 33 | for (std::size_t i = 0u; i < n; i++) 34 | asio::post(ctx); 35 | } 36 | 37 | #endif 38 | 39 | 40 | int main(int argc, char * argv[]) 41 | { 42 | { 43 | auto start = std::chrono::steady_clock::now(); 44 | cobalt::run(atest()); 45 | auto end = std::chrono::steady_clock::now(); 46 | printf("cobalt : %ld ms\n", std::chrono::duration_cast(end - start).count()); 47 | } 48 | 49 | { 50 | auto start = std::chrono::steady_clock::now(); 51 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 52 | asio::co_spawn(ctx, awtest(), asio::detached); 53 | ctx.run(); 54 | auto end = std::chrono::steady_clock::now(); 55 | printf("awaitable: %ld ms\n", std::chrono::duration_cast(end - start).count()); 56 | } 57 | 58 | #if defined(BOOST_COBALT_BENCH_WITH_CONTEXT) 59 | { 60 | auto start = std::chrono::steady_clock::now(); 61 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 62 | asio::spawn(ctx, stest, asio::detached); 63 | ctx.run(); 64 | auto end = std::chrono::steady_clock::now(); 65 | printf("stackful : %ld ms\n", std::chrono::duration_cast(end - start).count()); 66 | } 67 | #endif 68 | 69 | return 0; 70 | } -------------------------------------------------------------------------------- /boost-cobalt.jam: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Klemens D. Morgenstern 2 | # 3 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | import feature ; 8 | 9 | 10 | feature.feature boost.cobalt.pmr : std boost-container custom no : propagated composite ; 11 | feature.compose std : BOOST_COBALT_USE_STD_PMR=1 ; 12 | feature.compose boost-container : BOOST_COBALT_USE_BOOST_CONTAINER_PMR=1 ; 13 | feature.compose custom : BOOST_COBALT_USE_CUSTOM_PMR=1 ; 14 | feature.compose no : BOOST_COBALT_NO_PMR=1 ; 15 | 16 | feature.feature boost.cobalt.executor : any_io_executor use_io_context custom : propagated composite ; 17 | feature.compose any_io_executor : ; 18 | feature.compose use_io_context : BOOST_COBALT_USE_IO_CONTEXT=1 ; 19 | feature.compose custom_executor : BOOST_COBALT_CUSTOM_EXECUTOR=1 ; 20 | -------------------------------------------------------------------------------- /build.jam: -------------------------------------------------------------------------------- 1 | # Copyright René Ferdinand Rivera Morell 2024 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | require-b2 5.2 ; 7 | 8 | 9 | import boost-cobalt ; 10 | 11 | constant boost_dependencies : 12 | /boost/asio//boost_asio 13 | /boost/callable_traits//boost_callable_traits 14 | /boost/circular_buffer//boost_circular_buffer 15 | /boost/config//boost_config 16 | /boost/container//boost_container 17 | /boost/context//boost_context 18 | /boost/core//boost_core 19 | /boost/intrusive//boost_intrusive 20 | /boost/mp11//boost_mp11 21 | /boost/preprocessor//boost_preprocessor 22 | /boost/smart_ptr//boost_smart_ptr 23 | /boost/system//boost_system 24 | /boost/throw_exception//boost_throw_exception 25 | /boost/variant2//boost_variant2 ; 26 | 27 | project /boost/cobalt 28 | : common-requirements 29 | include 30 | ; 31 | 32 | explicit 33 | [ alias boost_cobalt : build//boost_cobalt ] 34 | [ alias all : boost_cobalt test example ] 35 | ; 36 | 37 | call-if : boost-library cobalt 38 | : install boost_cobalt 39 | ; 40 | 41 | -------------------------------------------------------------------------------- /build/Jamfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Klemens D. Morgenstern 2 | # 3 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | import os ; 8 | import-search /boost/config/checks ; 9 | import config : requires ; 10 | import-search /boost/cobalt ; 11 | import boost-cobalt ; 12 | 13 | 14 | project : requirements 15 | BOOST_ASIO_NO_DEPRECATED 16 | msvc:_SCL_SECURE_NO_WARNINGS 17 | msvc:_CRT_SECURE_NO_DEPRECATE 18 | msvc:/bigobj 19 | windows:WIN32_LEAN_AND_MEAN 20 | linux:-lpthread 21 | : source-location ../src 22 | : common-requirements $(boost_dependencies) 23 | ; 24 | 25 | 26 | local config-binding = [ modules.binding config ] ; 27 | config-binding ?= "" ; 28 | 29 | alias cobalt_sources 30 | : detail/exception.cpp 31 | detail/util.cpp 32 | channel.cpp 33 | error.cpp 34 | main.cpp 35 | this_thread.cpp 36 | thread.cpp 37 | ; 38 | 39 | explicit cobalt_sources ; 40 | 41 | lib boost_cobalt 42 | : cobalt_sources 43 | : requirements BOOST_COBALT_SOURCE=1 44 | shared:BOOST_COBALT_DYN_LINK=1 45 | [ requires 46 | cxx20_hdr_concepts 47 | ] 48 | boost-container:/boost/container//boost_container 49 | [ check-target-builds 50 | $(config-binding:D)//cpp_lib_memory_resource 51 | cpp_lib_memory_resource 52 | : @set-pmr-std 53 | : @set-pmr-boost 54 | ] 55 | 56 | : usage-requirements 57 | boost-container:/boost/container//boost_container 58 | shared:BOOST_COBALT_DYN_LINK=1 59 | BOOST_COBALT_NO_LINK=1 60 | [ check-target-builds 61 | $(config-binding:D)//cpp_lib_memory_resource 62 | cpp_lib_memory_resource 63 | : @set-pmr-std 64 | : @set-pmr-boost 65 | ] 66 | ; 67 | 68 | rule set-pmr-boost ( props * ) 69 | { 70 | if ! in $(props:G) 71 | { 72 | return boost-container ; 73 | } 74 | 75 | if boost-container in $(props) 76 | { 77 | return boost-container ; 78 | } 79 | } 80 | 81 | rule set-pmr-std ( props * ) 82 | { 83 | if ! in $(props:G) 84 | { 85 | return std ; 86 | } 87 | 88 | if std in $(props) 89 | { 90 | return std ; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cmake/CheckRequirements.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Klemens D. Morgenstern 2 | # 3 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) 8 | message(STATUS "Boost.Cobalt: not building, compiler doesn't support C++20.") 9 | return() 10 | endif() 11 | 12 | if(MSVC_VERSION AND MSVC_VERSION LESS 1930) 13 | message(STATUS "Boost.Cobalt: not building, the lowest supported MSVC version is 1930. ${MSVC_VERSION} is not supported") 14 | return() 15 | endif() 16 | 17 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 18 | 19 | try_compile( 20 | BOOST_COBALT_HAS_COROUTINE_INCLUDE 21 | ${CMAKE_CURRENT_BINARY_DIR} 22 | ${CMAKE_CURRENT_LIST_DIR}/coroutine.cpp 23 | CXX_STANDARD 20 24 | CXX_STANDARD_REQUIRED 20 25 | OUTPUT_VARIABLE TRY_COMPILE_OUTPUT) 26 | 27 | if (NOT BOOST_COBALT_HAS_COROUTINE_INCLUDE) 28 | message(STATUS "Boost.Cobalt: not building, can't include .") 29 | message(DEBUG ${TRY_COMPILE_OUTPUT}) 30 | return() 31 | endif() 32 | 33 | try_compile( 34 | BOOST_COBALT_HAS_CONCEPTS 35 | ${CMAKE_CURRENT_BINARY_DIR} 36 | ${CMAKE_CURRENT_LIST_DIR}/concepts.cpp 37 | CXX_STANDARD 20 38 | CXX_STANDARD_REQUIRED 20 39 | OUTPUT_VARIABLE TRY_COMPILE_OUTPUT) 40 | 41 | if (NOT BOOST_COBALT_HAS_CONCEPTS) 42 | message(STATUS "Boost.Cobalt: not building, can't include or use them.") 43 | message(DEBUG ${TRY_COMPILE_OUTPUT}) 44 | return() 45 | endif() 46 | 47 | set(BOOST_COBALT_SHOULD_USE_CONTAINER OFF) 48 | 49 | try_compile(BOOST_COBALT_HAS_STD_PMR 50 | ${CMAKE_CURRENT_BINARY_DIR} 51 | ${CMAKE_CURRENT_LIST_DIR}/memory_resource.cpp 52 | CXX_STANDARD 17 53 | CXX_STANDARD_REQUIRED 17) 54 | 55 | if (NOT BOOST_COBALT_HAS_STD_PMR) 56 | set(BOOST_COBALT_SHOULD_USE_CONTAINER ON) 57 | endif() 58 | 59 | set(BOOST_COBALT_REQUIREMENTS_MATCHED ON) 60 | 61 | -------------------------------------------------------------------------------- /cmake/concepts.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | static_assert(!std::derived_from); 10 | static_assert(std::same_as); 11 | static_assert(std::convertible_to); 12 | -------------------------------------------------------------------------------- /cmake/coroutine.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include -------------------------------------------------------------------------------- /cmake/memory_resource.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | std::pmr::monotonic_buffer_resource res; -------------------------------------------------------------------------------- /doc/Jamfile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Klemens D. Morgenstern 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # https://www.boost.org/LICENSE_1_0.txt 4 | 5 | import asciidoctor ; 6 | 7 | html index.html : index.adoc ; 8 | 9 | install images : $(images) : html/images ; 10 | install html_ : index.html : html : images ; 11 | 12 | pdf cobalt.pdf : index.adoc ; 13 | explicit cobalt.pdf ; 14 | 15 | install pdf_ : cobalt.pdf : pdf ; 16 | explicit pdf_ ; 17 | 18 | 19 | 20 | 21 | alias boostdoc ; 22 | explicit boostdoc ; 23 | alias boostrelease : html_ ; 24 | explicit boostrelease ; 25 | 26 | -------------------------------------------------------------------------------- /doc/acknowledgements.adoc: -------------------------------------------------------------------------------- 1 | = Acknowledgements 2 | 3 | This library would not have been possible without the CppAlliance and its founder Vinnie Falco. 4 | Vinnie trusted me enough to let me work on this project, while himself having very different views on how such a library should be designed. 5 | 6 | Thanks also go to Ruben Perez & Richard Hodges for listening to my design problems and giving me advice & use-cases. Furthermore, this library would not have been possible without the great boost.asio by Chris Kohlhoff. 7 | 8 | -------------------------------------------------------------------------------- /doc/background/asio_awaitable.adoc: -------------------------------------------------------------------------------- 1 | [#asio::awaitable] 2 | == `asio::awaitable` 3 | 4 | The `asio::awaitable` class is a very useful coroutine utility 5 | that works in multithreaded environments. 6 | 7 | Due to some implementation details it is incompatible with any other `awaitable` type. 8 | Thus, it is not possibly to `co_await` an `asio::awaitable` from any of the coroutine types 9 | in this library, nor is it possible to `co_await` any of those types from within an 10 | `asio::awaitable`. They can however interact through `asio::co_spawn` or <>. 11 | 12 | The way `asio::awaitable` blocks `co_await`-ing is by strict `await_transform` in its promises. 13 | It prohibits being awaited by a strict definition of `await_suspend`: 14 | 15 | [source,cpp] 16 | ---- 17 | template 18 | struct awaitable 19 | { 20 | // ... 21 | template 22 | void await_suspend( 23 | std::coroutine_handle> h); 24 | // ... 25 | 26 | }; 27 | ---- -------------------------------------------------------------------------------- /doc/background/lazy_eager.adoc: -------------------------------------------------------------------------------- 1 | [#lazy] [#eager] 2 | == Lazy & eager 3 | 4 | Coroutines are lazy if they only start execution of its code after it gets resumed, while an eager one will execute right-away until its first suspension point (i.e. a `co_await`, `co_yield` or `co_return` expression.) 5 | 6 | [source,cpp] 7 | ---- 8 | lazy_coro co_example() 9 | { 10 | printf("Entered coro\n"); 11 | co_yield 0; 12 | printf("Coro done\n"); 13 | } 14 | 15 | int main() 16 | { 17 | printf("enter main\n"); 18 | auto lazy = co_example(); 19 | printf("constructed coro\n"); 20 | lazy.resume(); 21 | printf("resumed once\n"); 22 | lazy.resume(); 23 | printf("resumed twice\n"); 24 | return 0; 25 | } 26 | ---- 27 | 28 | Which will produce output like this: 29 | 30 | [source] 31 | ---- 32 | enter main 33 | constructed coro 34 | Entered coro 35 | resumed once 36 | Coro Done 37 | resumed twice 38 | ---- 39 | 40 | ifdef::generate-diagram[] 41 | [mermaid, target=lazy_eager1] 42 | ---- 43 | sequenceDiagram 44 | participant main; 45 | Note left of main: "enter main" 46 | main-->>+lazy: co_example() 47 | Note left of main: "constructed coro" 48 | main->>lazy: resume() 49 | Note right of lazy: "Entered coro 50 | lazy-->>main: co_yield 0 51 | Note left of main: "resumed once" 52 | main-->>+lazy: resume() 53 | Note right of lazy: "Coro done" 54 | lazy->>main: co_return 55 | Note left of main: "resumed twice" 56 | ---- 57 | endif::[] 58 | 59 | ifndef::generate-diagram[] 60 | image::lazy_eager1.png[] 61 | endif::[] 62 | 63 | 64 | Whereas an eager coro would look like this: 65 | 66 | [source,cpp] 67 | ---- 68 | eager_coro co_example() 69 | { 70 | printf("Entered coro\n"); 71 | co_yield 0; 72 | printf("Coro done\n"); 73 | } 74 | 75 | int main() 76 | { 77 | printf("enter main\n"); 78 | auto lazy = co_example(); 79 | printf("constructed coro\n"); 80 | lazy.resume(); 81 | printf("resumed once\n"); 82 | return 0; 83 | } 84 | ---- 85 | 86 | Which will produce output like this: 87 | 88 | [source] 89 | ---- 90 | enter main 91 | Entered coro 92 | constructed coro 93 | resume once 94 | Coro Done 95 | ---- 96 | 97 | 98 | ifdef::generate-diagram[] 99 | [mermaid, target=lazy_eager2] 100 | ---- 101 | sequenceDiagram 102 | participant main; 103 | Note left of main: "enter main" 104 | main->>lazy: co_example() 105 | Note right of lazy: "Entered coro 106 | lazy-->>main: co_yield 0 107 | Note left of main: "constructed coro" 108 | main-->>+lazy: resume() 109 | Note right of lazy: "Coro done" 110 | lazy->>main: co_return 111 | Note left of main: "resumed once" 112 | ---- 113 | endif::[] 114 | 115 | ifndef::generate-diagram[] 116 | image::lazy_eager2.png[] 117 | endif::[] 118 | -------------------------------------------------------------------------------- /doc/background/stackless.adoc: -------------------------------------------------------------------------------- 1 | [#stackless] 2 | == Stackless 3 | 4 | C++20 coroutines are stackless, meaning they don't have their own stack. 5 | 6 | A stack in C++ describes the callstack, i.e. all the function frames stacked. 7 | A function frame is the memory a function needs to operate, i.e. a slice of memory 8 | to store its variables and information such as the return address. 9 | 10 | NOTE: The size of a function frame is known at compile time, but not outside the compile unit containing its definition. 11 | 12 | [source, cpp] 13 | ---- 14 | 15 | int bar() {return 0;} // the deepest point of the stack 16 | int foo() {return bar();} 17 | 18 | int main() 19 | { 20 | return foo(); 21 | } 22 | ---- 23 | 24 | The call stack in the above example is: 25 | 26 | [source] 27 | ---- 28 | main() 29 | foo() 30 | bar() 31 | ---- 32 | 33 | ifdef::generate-diagram[] 34 | [mermaid, target=stackless1] 35 | ---- 36 | sequenceDiagram 37 | main->>+foo: call 38 | foo->>+bar: call 39 | bar->>-foo: return 40 | foo->>-main: return 41 | ---- 42 | endif::[] 43 | 44 | ifndef::generate-diagram[] 45 | image::stackless1.png[] 46 | endif::[] 47 | 48 | Coroutines can be implemented as stackful, which means that it allocates a fixes chunk of memory and stacks function frames similar to a thread. 49 | C++20 coroutines are stackless, i.e. they only allocate their own frame and use the callers stack on resumption. Using our previous example: 50 | 51 | [source,cpp] 52 | ---- 53 | fictional_eager_coro_type example() 54 | { 55 | co_yield 0; 56 | co_yield 1; 57 | } 58 | 59 | void nested_resume(fictional_eager_coro_type& f) 60 | { 61 | f.resume(); 62 | } 63 | 64 | int main() 65 | { 66 | auto f = example(); 67 | nested_resume(f); 68 | f.reenter(); 69 | return 0; 70 | } 71 | ---- 72 | 73 | This will yield a call stack similar to this: 74 | 75 | [source] 76 | ---- 77 | main() 78 | f$example() 79 | nested_resume() 80 | f$example() 81 | f$example() 82 | ---- 83 | 84 | 85 | ifdef::generate-diagram[] 86 | [mermaid, target=stackless2] 87 | ---- 88 | sequenceDiagram 89 | participant main 90 | participant nested_resume 91 | main->>+example: create & call 92 | example-->>main: co_yield 93 | main->>+nested_resume: call 94 | nested_resume-->>example: resume 95 | example-->>nested_resume: co_yield 96 | nested_resume->>-main: return 97 | main-->>example: resume 98 | example->>-main: co_return 99 | ---- 100 | endif::[] 101 | 102 | ifndef::generate-diagram[] 103 | image::stackless2.png[] 104 | endif::[] 105 | 106 | The same applies if a coroutine gets moved accross threads. -------------------------------------------------------------------------------- /doc/compiler.adoc: -------------------------------------------------------------------------------- 1 | = Requirements 2 | 3 | == Libraries 4 | 5 | Boost.cobalt requires a C++20 compilers and directly depends on the following boost libraries: 6 | 7 | - boost.asio 8 | - boost.system 9 | - boost.circular_buffer 10 | - boost.intrusive 11 | - boost.smart_ptr 12 | - boost.container (for clang < 16) 13 | 14 | 15 | == Compiler 16 | 17 | This library is supported since Clang 14, Gcc 10 & MSVC 19.30 (Visual Studio 2022). 18 | 19 | IMPORTANT: Gcc versions 12.1 and 12.2 appear to have a bug for coroutines with out stack variables 20 | as can be seen [here](https://godbolt.org/z/6adGcqP1z) and should be avoided for coroutines. 21 | 22 | Clang only added `std::pmr` support in 16, so older clang versions use `boost::contianer::pmr` as a drop-in replacement. 23 | 24 | WARNING: Some if not all MSVC versions have a broken coroutine implementation, 25 | that this library needs to workaround. This may cause non-deterministic behaviour and overhead. 26 | 27 | A coroutine continuation may be done in the awaitable returned from a `final_suspend`, like this: 28 | 29 | [source,cpp] 30 | ---- 31 | // in promise 32 | auto final_suspend() noexcept 33 | { 34 | struct final_awaitable 35 | { 36 | std::coroutine_handle continuation{std::noop_coroutine()}; // <1> 37 | bool await_ready() const noexcept; 38 | std::coroutine_handle await_suspend(std::coroutine_handle h) noexcept 39 | { 40 | auto cc = continuation; 41 | h.destroy(); // <2> 42 | return cc; 43 | } 44 | 45 | void await_resume() noexcept {} 46 | }; 47 | return final_awaitable{my_continuation}; 48 | }; 49 | ---- 50 | <1> The continuation 51 | <2> Self-destroying the coroutine before continuation 52 | 53 | The final_suspend does not properly suspend the coroutine on MSVC, so that the `h.destroy()` will cause 54 | double destruction of elements on the coroutine frame. 55 | Therefor, msvc will need to post the destruction, to do it out of line. 56 | This will cause overhead and make the actual freeing of memory not deterministic. 57 | 58 | -------------------------------------------------------------------------------- /doc/design/associators.adoc: -------------------------------------------------------------------------------- 1 | [#associators] 2 | == Associators 3 | 4 | `cobalt` uses the `associator` concept of asio, but simplifies it. 5 | That is, it has three associators that are member functions of an awaiting promise. 6 | 7 | - `const executor_type & get_executor()` (always `executor`, must return by const ref) 8 | - `allocator_type get_allocator()` (always `pmr::polymorphic_allocator`) 9 | - `cancellation_slot_type get_cancellation_slot()` (must have the same IF as `asio::cancellation_slot`) 10 | 11 | `cobalt` uses concepts to check if those are present in its `await_suspend` functions. 12 | 13 | That way custom coroutines can support cancellation, executors and allocators. 14 | 15 | In a custom awaitable you can obtain them like this: 16 | 17 | [source,cpp] 18 | ---- 19 | struct my_awaitable 20 | { 21 | bool await_ready(); 22 | template 23 | void await_suspend(std::corutine_handle

h) 24 | { 25 | if constexpr (requires (Promise p) {p.get_executor();}) 26 | handle_executor(h.promise().get_executor(); 27 | 28 | if constexpr (requires (Promise p) {p.get_cancellation_slot();}) 29 | if ((cl = h.promise().get_cancellation_slot()).is_connected()) 30 | cl.emplace(); 31 | } 32 | 33 | void await_resume(); 34 | }; 35 | ---- 36 | 37 | Cancellation gets connected in a `co_await` expression (if supported by the coroutine & awaitable), 38 | including synchronization mechanism like <>. 39 | 40 | -------------------------------------------------------------------------------- /doc/design/concepts.adoc: -------------------------------------------------------------------------------- 1 | [#design:concepts] 2 | == Concepts 3 | 4 | This library has two fundamental concepts: 5 | 6 | - <> 7 | - coroutine 8 | 9 | An <> is an expression that can be used with `co_await` 10 | from within a coroutine, e.g.: 11 | 12 | [source,cpp] 13 | ---- 14 | co_await delay(50ms); 15 | ---- 16 | 17 | However, a coroutine promise can define an `await_transform`, 18 | i.e. what is actually valid to use with `co_await` expression depends on the coroutine. 19 | 20 | Thus, we should redefine what an <> is: 21 | An *awaitable* is a type that can be `co_await`-ed from within a coroutine, 22 | which promise does not define `await_transform`. 23 | 24 | 25 | A `pseudo-keyword` is a type that can be used in a coroutines that is adds special 26 | functionality for it due to its promise `await_transform`. 27 | 28 | All the verbs in the <> namespace are such pseudo-keywords. 29 | 30 | [source,cpp] 31 | ---- 32 | auto exec = co_await this_coro::executor; 33 | ---- 34 | 35 | NOTE: This library exposes a set of `enable_*` base classes for promises, 36 | to make the creation of custom coroutines easy. 37 | This includes the <>, which provides an `await_transform` 38 | that just forward <>. 39 | 40 | A coroutine in the context of this documentation refers 41 | to an asynchronous coroutine, i.e. synchronous coroutines like 42 | link:https://en.cppreference.com/w/cpp/coroutine/generator[std::generator] 43 | are not considered. 44 | 45 | All coroutines except <> are also actual <>. 46 | 47 | -------------------------------------------------------------------------------- /doc/design/promise.adoc: -------------------------------------------------------------------------------- 1 | [#design:promise] 2 | == Promise 3 | 4 | The main coroutine type is a `promise`, which is eager. 5 | The reason to default to this, is that the compiler can optimize out 6 | promises that do not suspend, like this: 7 | 8 | [source,cpp] 9 | ---- 10 | cobalt::promise noop() 11 | { 12 | co_return; 13 | } 14 | ---- 15 | 16 | Awaiting the above operation is in theory a noop, 17 | but practically speaking, compilers aren't there as of 2023. 18 | 19 | -------------------------------------------------------------------------------- /doc/design/race.adoc: -------------------------------------------------------------------------------- 1 | [#design:race] 2 | == Race 3 | 4 | The most important synchronization mechanism is the `race` function. 5 | 6 | It awaits multiple <>s in a pseudo-random order 7 | and will return the result of the first one completion, before disregarding the rest. 8 | 9 | That is, it initiates the `co_await` in a pseudo-random order and stops once one 10 | awaitable is found to be ready or completed immediately. 11 | 12 | [source,cpp] 13 | ---- 14 | cobalt::generator gen1(); 15 | cobalt::generator gen2(); 16 | 17 | cobalt::promise p() 18 | { 19 | auto g1 = gen1(); 20 | auto g2 = gen2(); 21 | while (!co_await cobalt::this_coro::cancelled) 22 | { 23 | switch(auto v = co_await race(g1, g2); v.index()) 24 | { 25 | case 0: 26 | printf("Got int %d\n", get<0>(v)); 27 | break; 28 | case 1: 29 | printf("Got double %f\n", get<1>(v)); 30 | break; 31 | } 32 | } 33 | } 34 | ---- 35 | 36 | The `race` must however internally wait for all awaitable to complete 37 | once it initiates to `co_await`. 38 | Therefore, once the first <> completes, 39 | it tries to <> the rest, and if that fails cancels them. 40 | 41 | `race` is the preferred way to trigger cancellations, e.g.: 42 | 43 | [source,cpp] 44 | ---- 45 | cobalt::promise timeout(); 46 | cobalt::promise work(); 47 | 48 | race(timeout(), work()); 49 | ---- 50 | 51 | [#design:interrupt_await] 52 | == interrupt_await 53 | 54 | If it naively cancelled it would however lose data. 55 | Thus, the concept of `interrupt_await` is introduced, 56 | which tells the awaitable (that supports it) 57 | to immediately resume the awaiter and return or throw an ignored value. 58 | 59 | .Example of an interruptible awaitable 60 | [source,cpp] 61 | ---- 62 | struct awaitable 63 | { 64 | bool await_ready() const; 65 | 66 | template 67 | std::coroutine_handle await_suspend(std::coroutine_handle h); 68 | 69 | T await_resume(); 70 | 71 | void interrupt_await() &; 72 | }; 73 | ---- 74 | 75 | If the `interrupt_await` doesn't result in immediate resumption (of `h`), 76 | `race` will send a cancel signal. 77 | 78 | `race` applies these with the correct reference qualification: 79 | 80 | [source,cpp] 81 | ---- 82 | auto g = gen1(); 83 | race(g, gen2()); 84 | ---- 85 | 86 | The above will call a `interrupt_await() &` function for `g1` and `interrupt_await() &&` for `g2` if available. 87 | 88 | NOTE: Generally speaking, the coroutines in `cobalt` support lvalue interruption, i.e. `interrupt_await() &`. 89 | <> operations are unqualified, i.e. work in both cases. 90 | 91 | <> and <> will forward interruptions, 92 | i.e. this will only interrupt `g1` and `g2` if `gen2()` completes first: 93 | 94 | -------------------------------------------------------------------------------- /doc/design/thread.adoc: -------------------------------------------------------------------------------- 1 | == Threading 2 | 3 | This library is single-threaded by design, because this simplifies resumption 4 | and thus more performant handling of synchronizations like <>. 5 | <> would need to lock every raceed awaitable to avoid data loss 6 | which would need to be blocking and get worse with every additional element. 7 | 8 | IMPORTANT: you can't have any coroutines be resumed on a different thread than created on, 9 | except for a <> (e.g. using <>). 10 | 11 | The main technical reason is that the most efficient way of switching coroutines is by returning the handle 12 | of the new coroutine from `await_suspend` like this: 13 | 14 | [source,cpp] 15 | ---- 16 | struct my_awaitable 17 | { 18 | bool await_ready(); 19 | std::coroutine_handle await_suspend(std::coroutine_handle); 20 | void await_resume(); 21 | }; 22 | ---- 23 | 24 | In this case, the awaiting coroutine will be suspended before await_suspend is called, 25 | and the coroutine returned is resumed. This of course doesn't work if we need to go through an executor. 26 | 27 | This doesn't only apply to awaited coroutines, but channels, too. 28 | The channels in this library use an intrusive list of awaitables 29 | and may return the handle of reading (and thus suspended) coroutine 30 | from a write_operation's `await_suspend`. 31 | 32 | 33 | -------------------------------------------------------------------------------- /doc/images/awaitables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/awaitables.png -------------------------------------------------------------------------------- /doc/images/generators1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/generators1.png -------------------------------------------------------------------------------- /doc/images/generators2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/generators2.png -------------------------------------------------------------------------------- /doc/images/lazy_eager1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/lazy_eager1.png -------------------------------------------------------------------------------- /doc/images/lazy_eager2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/lazy_eager2.png -------------------------------------------------------------------------------- /doc/images/stackless1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/stackless1.png -------------------------------------------------------------------------------- /doc/images/stackless2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/doc/images/stackless2.png -------------------------------------------------------------------------------- /doc/index.adoc: -------------------------------------------------------------------------------- 1 | = Documentation boost.cobalt 2 | Klemens Morgenstern 3 | Version 0.1, 29.01.2023 4 | :source-highlighter: rouge 5 | :toc: left 6 | :toclevels: 4 7 | :icons: font 8 | :idprefix: 9 | :docinfo: private-footer 10 | :source-highlighter: rouge 11 | :source-language: c++ 12 | :example-caption: Example 13 | 14 | :imagesdir: ../images 15 | 16 | :leveloffset: +1 17 | 18 | include::overview.adoc[] 19 | include::motivation.adoc[] 20 | 21 | = Coroutine Primer 22 | 23 | include::primer/async.adoc[] 24 | include::primer/coroutines.adoc[] 25 | include::primer/awaitables.adoc[] 26 | include::primer/event-loops.adoc[] 27 | 28 | = Tour 29 | 30 | include::tour/entry.adoc[] 31 | include::tour/promise.adoc[] 32 | include::tour/task.adoc[] 33 | include::tour/generator.adoc[] 34 | include::tour/join.adoc[] 35 | include::tour/race.adoc[] 36 | 37 | = Tutorial 38 | 39 | include::tutorial/delay.adoc[] 40 | include::tutorial/echo_server.adoc[] 41 | include::tutorial/ticker.adoc[] 42 | include::tutorial/delay_op.adoc[] 43 | include::tutorial/push_generator.adoc[] 44 | include::tutorial/advanced.adoc[] 45 | 46 | 47 | = Design 48 | 49 | include::design/concepts.adoc[] 50 | include::design/thread_local.adoc[] 51 | include::design/promise.adoc[] 52 | include::design/race.adoc[] 53 | include::design/associators.adoc[] 54 | include::design/thread.adoc[] 55 | 56 | = Reference 57 | 58 | include::reference/main.adoc[] 59 | include::reference/promise.adoc[] 60 | include::reference/generators.adoc[] 61 | include::reference/task.adoc[] 62 | include::reference/detached.adoc[] 63 | include::reference/op.adoc[] 64 | include::reference/concepts.adoc[] 65 | include::reference/this_coro.adoc[] 66 | include::reference/this_thread.adoc[] 67 | include::reference/channel.adoc[] 68 | include::reference/with.adoc[] 69 | include::reference/race.adoc[] 70 | include::reference/gather.adoc[] 71 | include::reference/join.adoc[] 72 | include::reference/wait_group.adoc[] 73 | include::reference/spawn.adoc[] 74 | include::reference/run.adoc[] 75 | include::reference/thread.adoc[] 76 | include::reference/result.adoc[] 77 | include::reference/async_for.adoc[] 78 | include::reference/error.adoc[] 79 | include::reference/config.adoc[] 80 | 81 | include::reference/experimental/context.adoc[] 82 | 83 | 84 | = In-Depth 85 | 86 | include::background/custom_executors.adoc[] 87 | include::background/stackless.adoc[] 88 | include::background/lazy_eager.adoc[] 89 | 90 | include::benchmarks.adoc[] 91 | 92 | include::compiler.adoc[] 93 | 94 | include::acknowledgements.adoc[] 95 | 96 | 97 | :leveloffset: -1 -------------------------------------------------------------------------------- /doc/motivation.adoc: -------------------------------------------------------------------------------- 1 | = Motivation 2 | 3 | Many languages programming languages 4 | like node.js and python provide easy to use single-threaded concurrency frameworks. 5 | While more complex than synchronous code, 6 | single threaded asynchronicity avoids many of the pitfalls & overhead of multi-threading. 7 | 8 | That is, one coroutine can work, while others wait for events (e.g. a response from a server). 9 | This allows to write applications that *do multiple things simultaneously* on a *single thread*. 10 | 11 | This library is meant to provide this to C++: *simple single threaded asynchronicity* 12 | akin to node.js and asyncio in python that works with existing libraries like 13 | `boost.beast`, `boost.mysql` or `boost.redis`. 14 | It based on `boost.asio`. 15 | 16 | It takes a collection of concepts from other languages and provides them based on C++20 coroutines. 17 | 18 | - easy asynchronous base functions, such as an async <> & <> 19 | - <> & <> types 20 | - <> 21 | - an <> 22 | - <> 23 | - <> 24 | 25 | Unlike `asio::awaitable` and `asio::experimental::coro`, `cobalt` coroutines are open. 26 | That is, an `asio::awaitable` can only await and be awaited by other `asio::awaitable` 27 | and does not provide coroutine specific synchronization mechanisms. 28 | 29 | `cobalt` on the other hand provides a coroutine specific `channel` 30 | and different wait types (`race`, `gather` etc.) that are optimized 31 | to work with coroutines and awaitables. 32 | 33 | -------------------------------------------------------------------------------- /doc/overview.adoc: -------------------------------------------------------------------------------- 1 | = Overview 2 | 3 | Here's a list of relevant features in cobalt: 4 | 5 | .Coroutine types 6 | [cols="1,5"] 7 | |=== 8 | |<> 9 | |An eager coroutine returning a single result- consider it the default 10 | 11 | |<> 12 | |An eager coroutine that can yield multiple values. 13 | 14 | |<> 15 | |A lazy version of <> that can be spawned onto other executors. 16 | 17 | |<> 18 | |A coroutine similar to promise, without a handle 19 | 20 | |=== 21 | 22 | 23 | .Synchronization Functions 24 | [cols="1,5"] 25 | |=== 26 | |<> 27 | |A function that waits for one coroutine out of a set that is ready in a pseudo-random way, to avoid starvation. 28 | 29 | |<> 30 | |A function that waits for a set of coroutines and returns all of them as value or throws an exception if any awaitable does so. 31 | 32 | 33 | |<> 34 | |A function that waits for a set of coroutines and returns all of them as `result`, capturing all exceptions individually. 35 | 36 | |<> 37 | |A deterministic `race` that evaluates left-to-right. 38 | |=== 39 | 40 | .Utilities 41 | [cols="1,5"] 42 | |=== 43 | |<> 44 | |A thread-local utility to send values between coroutines. 45 | 46 | 47 | |<> 48 | |An async RAII helper, that allows async teardown when exceptions occur 49 | 50 | |=== 51 | 52 | .Reading guide 53 | [cols="1,3,3"] 54 | |=== 55 | |<> 56 | |A short introduction to C++ coroutines 57 | |Read if you've never used coroutines before 58 | 59 | |<> 60 | |An abbreviated high level view of the features and concepts 61 | |Read if you're familiar with asio & coroutines and want a rough idea what this library offers. 62 | 63 | |<> 64 | |Low level view of usages 65 | |Read if you want to get coding quickly 66 | 67 | |<> 68 | |API reference 69 | |Look up details while coding 70 | 71 | |<> 72 | |Some implementation details 73 | |Read if you're not confused enough 74 | 75 | |=== 76 | 77 | -------------------------------------------------------------------------------- /doc/primer/async.adoc: -------------------------------------------------------------------------------- 1 | == Async programming 2 | 3 | Asynchronous programming generally refers to a style of programming 4 | that allows tasks to be run in the background, while the other works is performed. 5 | 6 | Imagine if you will a get-request function that performs a 7 | full http request including connecting & ssl handshakes etc. 8 | 9 | [source,cpp] 10 | ---- 11 | std::string http_get(std:string_view url); 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | auto res = http_get("https://boost.org"); 16 | printf("%s", res.c_str()); 17 | return 0; 18 | } 19 | ---- 20 | 21 | The above code would be traditional synchronous programming. If we want to perform 22 | two requests in parallel we would need to create another thread to run another thread 23 | with synchronous code. 24 | 25 | [source,cpp] 26 | ---- 27 | std::string http_get(std:string_view url); 28 | 29 | int main(int argc, char * argv[]) 30 | { 31 | std::string other_res; 32 | 33 | std::thread thr{[&]{ other_res = http_get("https://cppalliance.org"); }}; 34 | auto res = http_get("https://boost.org"); 35 | thr.join(); 36 | 37 | printf("%s", res.c_str()); 38 | printf("%s", other_res.c_str()); 39 | return 0; 40 | } 41 | ---- 42 | 43 | This works, but our program will spend most of the time waiting for input. 44 | Operating systems provide APIs that allow IO to be performed asynchronously, 45 | and libraries such as https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio.html[boost.asio] 46 | provide portable ways to manage asynchronous operations. 47 | Asio itself does not dictate a way to handle the completions. 48 | This library (boost.cobalt) provides a way to manage this all through coroutines/awaitables. 49 | 50 | [source,cpp] 51 | ---- 52 | cobalt::promise http_cobalt_get(std:string_view url); 53 | 54 | cobalt::main co_main(int argc, char * argv[]) 55 | { 56 | auto [res, other_res] = 57 | cobalt::join( 58 | http_cobalt_get("https://boost.org"), 59 | http_cobalt_get("https://cppalliance.org") 60 | ); 61 | 62 | printf("%s", res.c_str()); 63 | printf("%s", other_res.c_str()); 64 | return 0; 65 | } 66 | ---- 67 | 68 | In the above code the asynchronous function to perform the request 69 | takes advantage of the operating system APIs so that the actual IO doesn't block. 70 | This means that while we're waiting for both functions to complete, 71 | the operations are interleaved and non-blocking. 72 | At the same time cobalt provides the coroutine primitives that keep us out of callback hell. 73 | 74 | 75 | -------------------------------------------------------------------------------- /doc/primer/awaitables.adoc: -------------------------------------------------------------------------------- 1 | == Awaitables 2 | 3 | Awaitables are types that can be used in a `co_await` expression. 4 | 5 | [source,cpp,subs="+quotes"] 6 | ---- 7 | struct awaitable_prototype 8 | { 9 | bool await_ready(); 10 | 11 | template 12 | __see_below__ await_suspend(std::coroutine_handle); 13 | 14 | __return_type__ await_resume(); 15 | }; 16 | ---- 17 | 18 | NOTE: Type will be implicitly converted into an awaitable if there is an `operator co_await` call available. 19 | This documentation will use `awaitable` to include these types, 20 | and "actual_awaitable" to refer to type conforming to the above prototype. 21 | 22 | ifdef::generate-diagram[] 23 | [mermaid, target=awaitables] 24 | ---- 25 | flowchart TD 26 | aw{await_ready?} 27 | aw ---->|true| ar[await_resume] 28 | aw -->|false| as[await_suspend] 29 | as -->|Resume| ar 30 | ---- 31 | endif::[] 32 | 33 | ifndef::generate-diagram[] 34 | image::awaitables.png[] 35 | endif::[] 36 | 37 | In a `co_await` expression the waiting coroutine will first invoke 38 | `await_ready` to check if the coroutine needs to suspend. 39 | When ready, it goes directly to `await_resume` to get the value, 40 | as there is no suspension needed. 41 | Otherwise, it will suspend itself and call `await_suspend` with a 42 | `std::coroutine_handle` to its own promise. 43 | 44 | NOTE: `std::coroutine_handle` can be used for type erasure. 45 | 46 | 47 | The __return_type__ is the result type of the `co_await expression`, e.g. `int`: 48 | 49 | [source,cpp] 50 | ---- 51 | int i = co_await awaitable_with_int_result(); 52 | ---- 53 | 54 | The return type of the `await_suspend` can be three things: 55 | 56 | - `void` 57 | - `bool` 58 | - `std::coroutine_handle` 59 | 60 | If it is void the awaiting coroutine remains suspended. If it is `bool`, 61 | the value will be checked, and if false, the awaiting coroutine will resume right away. 62 | 63 | If a `std::coroutine_handle` is returned, this coroutine will be resumed. 64 | The latter allows `await_suspend` to return the handle passed in, 65 | being effectively the same as returning `false`. 66 | 67 | If the awaiting coroutine gets re-resumed right away, i.e. after calling await_resume, 68 | it is referred to as "immediate completion" within this library. 69 | This is not to be confused with a non-suspending awaitable, i.e. one that returns `true` from `await_ready`. 70 | 71 | -------------------------------------------------------------------------------- /doc/primer/coroutines.adoc: -------------------------------------------------------------------------------- 1 | == Coroutines 2 | 3 | Coroutines are resumable functions. 4 | Resumable means that a function can suspend, 5 | i.e. pass the control back to the caller multiple times. 6 | 7 | A regular function yields control back to the caller with the `return` function, where it also returns the value. 8 | 9 | A coroutine on the other hand might yield control to the caller and get resumed multiple times. 10 | 11 | A coroutine has three control keywords akin to co_return 12 | (of which only `co_return` has to be supported). 13 | 14 | - `co_return` 15 | - `co_yield` 16 | - `co_await` 17 | 18 | 19 | 20 | === `co_return` 21 | 22 | This is similar to `return`, but marks the function as a coroutine. 23 | 24 | === `co_await` 25 | 26 | The `co_await` expression suspends for an <>, 27 | i.e. stops execution until the `awaitable` resumes it. 28 | 29 | E.g.: 30 | 31 | [source,cpp] 32 | ---- 33 | cobalt::promise delay(std::chrono::milliseconds); 34 | 35 | cobalt::task example() 36 | { 37 | co_await delay(std::chrono::milliseconds(50)); 38 | } 39 | ---- 40 | 41 | A `co_await` expression can yield a value, depending on what it is awaiting. 42 | 43 | [source,cpp] 44 | ---- 45 | cobalt::promise read_some(); 46 | 47 | cobalt::task example() 48 | { 49 | std::string res = co_await read_some(); 50 | } 51 | ---- 52 | 53 | NOTE: In `cobalt` most coroutine primitives are also <>. 54 | 55 | === `co_yield` 56 | 57 | The `co_yield` expression is similar to the `co_await`, 58 | but it yields control to the caller and carries a value. 59 | 60 | For example: 61 | 62 | [source,cpp] 63 | ---- 64 | cobalt::generator iota(int max) 65 | { 66 | int i = 0; 67 | while (i < max) 68 | co_yield i++; 69 | 70 | co_return i; 71 | } 72 | ---- 73 | 74 | A `co_yield` expression can also produce a value, 75 | which allows the user of yielding coroutine to push values into it. 76 | 77 | [source,cpp] 78 | ---- 79 | cobalt::generator iota() 80 | { 81 | int i = 0; 82 | bool more = false; 83 | do 84 | { 85 | more = co_yield i++; 86 | } 87 | while(more); 88 | co_return -1; 89 | } 90 | ---- 91 | 92 | 93 | .Stackless 94 | **** 95 | C++ coroutine are stack-less, which means they only allocate their own function frame. 96 | 97 | See <> for more details. 98 | **** 99 | 100 | -------------------------------------------------------------------------------- /doc/primer/event-loops.adoc: -------------------------------------------------------------------------------- 1 | [#event-loops] 2 | == Event Loops 3 | 4 | Since the coroutines in `cobalt` can `co_await` events, they need to be run on an event-loop. 5 | That is another piece of code is responsible for tracking outstanding event and resume a resuming coroutines that are awaiting them. 6 | This pattern is very common and is used in a similar way by node.js or python's `asyncio`. 7 | 8 | `cobalt` uses an https://www.boost.org/doc/libs/master/doc/html/boost_asio/reference/io_context.html[`asio::io_context`] 9 | as its default event loop. That is, the classes <>, <> and the <> function 10 | are using it internally. 11 | 12 | You can use any event loop that can produce an https://www.boost.org/doc/libs/master/doc/html/boost_asio/reference/any_io_executor.html[`asio::any_io_executor`] 13 | with the library. The easiest way to achieve this is by using <>. 14 | 15 | The event loop is accessed through an executor (following the asio terminology) and can be manually set using <>. 16 | 17 | -------------------------------------------------------------------------------- /doc/reference/async_for.adoc: -------------------------------------------------------------------------------- 1 | [#async_for] 2 | == cobalt/async_for.hpp 3 | 4 | For types like generators a `BOOST_COBALT_FOR` macro is provided, to emulate an `for co_await` loop. 5 | 6 | 7 | [source,cpp] 8 | ---- 9 | cobalt::generator gen(); 10 | 11 | cobalt::main co_main(int argc, char * argv[]) 12 | { 13 | BOOST_COBALT_FOR(auto i, gen()) 14 | printf("Generated value %d\n", i); 15 | 16 | co_return 0; 17 | } 18 | 19 | ---- 20 | 21 | The requirement is that the <> used in the for loop has an `operator bool` to check if it 22 | can be awaited again. This is the case for <> and <>. 23 | 24 | -------------------------------------------------------------------------------- /doc/reference/channel.adoc: -------------------------------------------------------------------------------- 1 | [#channel] 2 | == cobalt/channel.hpp 3 | 4 | Channels can be used to exchange data between different coroutines 5 | on a single thread. 6 | 7 | === Outline 8 | 9 | .channel outline 10 | [example] 11 | [source,cpp,subs=+quotes] 12 | ---- 13 | include::../../include/boost/cobalt/channel.hpp[tag=outline] 14 | ---- 15 | 16 | === Description 17 | 18 | Channels are a tool for two coroutines to communicate and synchronize. 19 | 20 | [source,cpp] 21 | ---- 22 | const std::size_t buffer_size = 2; 23 | channel ch{exec, buffer_size}; 24 | 25 | // in coroutine <1> 26 | co_await ch.write(42); 27 | 28 | // in coroutine <2> 29 | auto val = co_await ch.read(); 30 | ---- 31 | <1> Send a value to the channel - will block until it can be sent 32 | <2> Read a value from the channel - will block until a value is awaitable. 33 | 34 | Both operations maybe be blocking depending on the channel buffer size. 35 | 36 | If the buffer size is zero, a `read` & `write` will need to occur at the same time, 37 | i.e. act as a rendezvous. 38 | 39 | If the buffer is not full, the write operation will not suspend the coroutine; 40 | likewise if the buffer is not empty, the read operation will not suspend. 41 | 42 | If two operations complete at once (as is always the case with an empty buffer), 43 | the second operation gets posted to the executor for later completion. 44 | 45 | NOTE: A channel type can be `void`, in which case `write` takes no parameter. 46 | 47 | The channel operations can be cancelled without losing data. 48 | This makes them usable with <>. 49 | 50 | [source,cpp] 51 | ---- 52 | generator> merge( 53 | channel & c1, 54 | channel & c2) 55 | { 56 | while (c1 && c2) 57 | co_yield co_await race(c1, c2); 58 | } 59 | ---- 60 | 61 | 62 | === Example 63 | 64 | [source,cpp] 65 | ---- 66 | include::../../example/channel.cpp[tag=channel_example] 67 | ---- 68 | 69 | Additionally, a `channel_reader` is provided to make reading channels more convenient & usable with 70 | <>. 71 | 72 | [source,cpp] 73 | ---- 74 | cobalt::main co_main(int argc, char * argv[]) 75 | { 76 | cobalt::channel c; 77 | 78 | auto p = producer(c); 79 | BOOST_COBALT_FOR(int value, cobalt::channel_reader(c)) 80 | std::cout << value << std::endl; 81 | 82 | co_await p; 83 | co_return 0; 84 | } 85 | ---- 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/reference/concepts.adoc: -------------------------------------------------------------------------------- 1 | [#concepts] 2 | == cobalt/concepts.hpp 3 | 4 | [#awaitable] 5 | === Awaitable 6 | 7 | An awaitable is an expression that can be used with `co_await`. 8 | 9 | [source,cpp] 10 | ---- 11 | include::../../include/boost/cobalt/concepts.hpp[tag=outline] 12 | ---- 13 | 14 | WARNING: <> in this library require that the coroutine promise 15 | return their executor by const reference if they provide one. Otherwise it'll use `this_thread::get_executor()`. 16 | 17 | [#enable_awaitables] 18 | === Enable awaitables 19 | 20 | Inheriting `enable_awaitables` will enable a coroutine to co_await anything through `await_transform` 21 | that would be `co_await`-able in the absence of any `await_transform`. -------------------------------------------------------------------------------- /doc/reference/config.adoc: -------------------------------------------------------------------------------- 1 | [#config] 2 | == cobalt/config.hpp 3 | 4 | The config adder allows to config some implementation details of boost.cobalt. 5 | 6 | === executor_type 7 | 8 | The executor type defaults to `boost::asio::any_io_executor`. 9 | 10 | You can set it to `boost::asio::any_io_executor` by defining `BOOST_COBALT_CUSTOM_EXECUTOR` 11 | and adding a `boost::cobalt::executor` type yourself. 12 | 13 | Alternatively, `BOOST_COBALT_USE_IO_CONTEXT` can be defined 14 | to set the executor to `boost::asio::io_context::executor_type`. 15 | 16 | === pmr 17 | 18 | Boost.cobalt can be used with different pmr implementations, defaulting to `std::pmr`. 19 | 20 | The following macros can be used to configure it: 21 | 22 | - `BOOST_COBALT_USE_STD_PMR` 23 | - `BOOST_COBALT_USE_BOOST_CONTAINER_PMR` 24 | - `BOOST_COBALT_USE_CUSTOM_PMR` 25 | 26 | 27 | If you define `BOOST_COBALT_USE_CUSTOM_PMR` you will need to provide a `boost::cobalt::pmr` namespace, 28 | that is a drop-in replacement for `std::pmr`. 29 | 30 | Alternatively, the `pmr` use can be disabled with 31 | 32 | - `BOOST_COBALT_NO_PMR`. 33 | 34 | In this case, cobalt will use a non-pmr monotonic resource for the 35 | synchronization functions (<>, <> and <>). 36 | 37 | `use_op` uses a small-buffer-optimized resource which's size can be set by defining 38 | `BOOST_COBALT_SBO_BUFFER_SIZE` and defaults to 4096 bytes. -------------------------------------------------------------------------------- /doc/reference/detached.adoc: -------------------------------------------------------------------------------- 1 | [#detached] 2 | == cobalt/detached.hpp 3 | 4 | A detached is an eager coroutine that can `co_await` but not `co_return` values. 5 | That is, it cannot be resumed and is usually not awaited. 6 | 7 | [source,cpp] 8 | ---- 9 | cobalt::detached delayed_print(std::chrono::milliseconds ms) 10 | { 11 | asio::steady_timer tim{co_await cobalt::this_coro::executor, ms}; 12 | co_await tim.async_wait(cobalt::use_op); 13 | printf("Hello world\n"); 14 | } 15 | 16 | cobalt::main co_main(int argc, char *argv[]) 17 | { 18 | delayed_print(); 19 | co_return 0; 20 | } 21 | ---- 22 | 23 | Detached is used to run coroutines in the background easily. 24 | 25 | [source,cpp] 26 | ---- 27 | cobalt::detached my_task(); 28 | 29 | cobalt::main co_main(int argc, char *argv[]) 30 | { 31 | my_task(); // <1> 32 | co_await delay(std::chrono::milliseconds(50)); 33 | co_return 0; 34 | } 35 | ---- 36 | <1> Spawn off the detached coro. 37 | 38 | 39 | A detached can assign itself a new cancellation source like this: 40 | 41 | [source,cpp] 42 | ---- 43 | 44 | cobalt::detached my_task(asio::cancellation_slot sl) 45 | { 46 | co_await this_coro::reset_cancellation_source(sl); 47 | // do somework 48 | } 49 | 50 | cobalt::main co_main(int argc, char *argv[]) 51 | { 52 | asio::cancellation_signal sig; 53 | my_task(sig.slot()); // <1> 54 | co_await delay(std::chrono::milliseconds(50)); 55 | sig.emit(asio::cancellation_type::all); 56 | co_return 0; 57 | } 58 | 59 | ---- 60 | 61 | === Executor 62 | [#detached-executor] 63 | 64 | The executor is taken from the `thread_local` <> function, unless a `asio::executor_arg` is used 65 | in any position followed by the executor argument. 66 | 67 | [source, cpp] 68 | ---- 69 | cobalt::detached my_gen(asio::executor_arg_t, asio::io_context::executor_type exec_to_use); 70 | ---- 71 | 72 | === Memory Resource 73 | [#detached-allocator] 74 | 75 | The memory resource is taken from the `thread_local` <> function, 76 | unless a `std::allocator_arg` is used in any position followed by a `polymorphic_allocator` argument. 77 | 78 | [source, cpp] 79 | ---- 80 | cobalt::detached my_gen(std::allocator_arg_t, pmr::polymorphic_allocator alloc); 81 | ---- 82 | 83 | [#detached-outline] 84 | === Outline 85 | 86 | 87 | [source,cpp] 88 | ---- 89 | struct detached {}; 90 | ---- 91 | <1> Supports <> 92 | 93 | [#detached-detached] 94 | === Promise 95 | 96 | The thread detached has the following properties. 97 | 98 | - <> 99 | - <> 100 | - <> 101 | - <> 102 | - <> 103 | - <> 104 | - <> 105 | -------------------------------------------------------------------------------- /doc/reference/error.adoc: -------------------------------------------------------------------------------- 1 | [#error] 2 | == cobalt/error.hpp 3 | 4 | In order to make errors easier to manage, cobalt provides an `error_category` to be used with 5 | `boost::system::error_code`. 6 | 7 | [source,cpp] 8 | ---- 9 | enum class error 10 | { 11 | moved_from, 12 | detached, 13 | completed_unexpected, 14 | wait_not_ready, 15 | already_awaited, 16 | allocation_failed 17 | }; 18 | 19 | system::error_category & cobalt_category(); 20 | system::error_code make_error_code(error e); 21 | ---- 22 | 23 | -------------------------------------------------------------------------------- /doc/reference/experimental/context.adoc: -------------------------------------------------------------------------------- 1 | [#context] 2 | == cobalt/experimental/context.hpp 3 | 4 | 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). 5 | 6 | This header provides `experimental` support for using `boost.fiber` based stackful coroutines 7 | as if they were C++20 coroutines. That is, they can use `awaitables` by being able to be put into a `coroutine_handle`. 8 | Likewise the implementation uses a C++20 coroutine promise and runs is as if it was a C++20 coroutine. 9 | 10 | [source,cpp] 11 | ---- 12 | // 13 | void delay(experimental::context> h, std::chrono::milliseconds ms) 14 | { 15 | asio::steady_timer tim{co_await cobalt::this_coro::executor, ms}; 16 | h.await(tim.async_wait(cobalt::use_op)); // instead of co_await. 17 | } 18 | 19 | cobalt::main co_main(int argc, char *argv[]) 20 | { 21 | cobalt::promise dl = cobalt::experimental::make_context(&delay, 50); 22 | co_await dl; 23 | co_return 0; 24 | } 25 | ---- 26 | 27 | === Reference 28 | 29 | [source,cpp] 30 | ---- 31 | // The internal coroutine context. 32 | /// Args are the function arguments after the handle. 33 | template 34 | struct context 35 | { 36 | // Get a handle to the promise 37 | promise_type & promise(); 38 | const promise_type & promise() const; 39 | 40 | // Convert it to any context if the underlying promise is the same 41 | template 42 | constexpr operator context() const; 43 | 44 | // Await something. Uses await_transform automatically. 45 | template 46 | auto await(Awaitable && aw); 47 | // Yield a value, if supported by the promise. 48 | template 49 | auto yield(Yield && value); 50 | }; 51 | 52 | 53 | // Create a fiber with a custom stack allocator (see boost.fiber for details) and explicit result (e.g. `promise`) 54 | template, Args...> Func, typename StackAlloc> 55 | auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args); 56 | 57 | // Create a fiber with the default allocator and explicit result (e.g. `promise`) 58 | template, Args...> Func> 59 | auto make_context(Func && func, Args && ... args); 60 | 61 | // Create a fiber with a custom stack allocator and implicit result (deduced from the first argument to func). 62 | template 63 | auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args); 64 | 65 | // Create a fiber with the default stack allocator and implicit result (deduced from the first argument to func). 66 | template 67 | auto make_context(Func && func, Args && ... args); 68 | ---- 69 | 70 | -------------------------------------------------------------------------------- /doc/reference/gather.adoc: -------------------------------------------------------------------------------- 1 | [#gather] 2 | == cobalt/gather.hpp 3 | 4 | The `gather` function can be used to `co_await` multiple <> 5 | at once with cancellations being passed through. 6 | 7 | The function will gather all completion and return them as `system::result`, 8 | i.e. capture conceptions as values. One awaitable throwing an exception will not cancel the others. 9 | 10 | It can be called as a variadic function with multiple <> or as on a range of <>. 11 | 12 | [source,cpp] 13 | ---- 14 | cobalt::promise task1(); 15 | cobalt::promise task2(); 16 | 17 | cobalt::promise do_gather() 18 | { 19 | co_await cobalt::gather(task1(), task2()); // <1> 20 | std::vector> aws {task1(), task2()}; 21 | co_await cobalt::gather(aws); // <2> 22 | } 23 | ---- 24 | <1> Wait for a variadic set of <> 25 | <2> Wait for a vector of <> 26 | 27 | The `gather` will invoke the functions of the `awaitable` as if used in a `co_await` expression. 28 | 29 | .Signatures of gather 30 | [source, cpp] 31 | ---- 32 | extern promise pv1, pv2; 33 | std::tuple, system::result> r1 = co_await gather(pv1, pv2); 34 | 35 | std::vector> pvv; 36 | pmr::vector> r2 = co_await gather(pvv); 37 | 38 | extern promise pi1, pi2; 39 | std::tuple, 40 | system::result, 41 | system::result, 42 | system::result> r3 = co_await gather(pv1, pv2, pi1, pi2); 43 | 44 | std::vector> piv; 45 | pmr::vector> r4 = co_await gather(piv); 46 | ---- 47 | 48 | 49 | [#gather-outline] 50 | === Outline 51 | 52 | [source,cpp,subs=+quotes] 53 | ---- 54 | // Variadic gather 55 | template 56 | __awaitable__ gather(Promise && ... p); 57 | 58 | // Ranged gather 59 | template> 60 | __awaitable__ gather(PromiseRange && p); 61 | ---- 62 | -------------------------------------------------------------------------------- /doc/reference/join.adoc: -------------------------------------------------------------------------------- 1 | [#join] 2 | == cobalt/join.hpp 3 | 4 | The `join` function can be used to `co_await` multiple <> at once with properly connected cancellations. 5 | 6 | The function will gather all completion and return them as values, unless an exception is thrown. 7 | If an exception is thrown, all outstanding ops are cancelled (or interrupted if possible) 8 | and the first exception gets rethrown. 9 | 10 | NOTE: `void` will be returned as `variant2::monostate` in the tuple, unless all awaitables yield void. 11 | 12 | It can be called as a variadic function with multiple <> or as on a range of <>. 13 | 14 | [source,cpp] 15 | ---- 16 | cobalt::promise task1(); 17 | cobalt::promise task2(); 18 | 19 | cobalt::promise do_join() 20 | { 21 | co_await cobalt::join(task1(), task2()); // <1> 22 | std::vector> aws {task1(), task2()}; 23 | co_await cobalt::join(aws); // <2> 24 | } 25 | ---- 26 | <1> Wait for a variadic set of <> 27 | <2> Wait for a vector of <> 28 | 29 | The `join` will invoke the functions of the `awaitable` as if used in a `co_await` expression. 30 | 31 | 32 | .Signatures of join 33 | [source, cpp] 34 | ---- 35 | extern promise pv1, pv2; 36 | /* void */ co_await join(pv1, pv2); 37 | 38 | std::vector> pvv; 39 | /* void */ co_await join(pvv); 40 | 41 | extern promise pi1, pi2; 42 | std::tuple r1 = co_await join(pv1, pv2, pi1, pi2); 43 | 44 | std::vector> piv; 45 | pmr::vector r2 = co_await join(piv); 46 | ---- 47 | 48 | 49 | [#join-outline] 50 | === Outline 51 | 52 | [source,cpp,subs=+quotes] 53 | ---- 54 | // Variadic join 55 | template 56 | __awaitable__ join(Promise && ... p); 57 | 58 | // Ranged join 59 | template> 60 | __awaitable__ join(PromiseRange && p); 61 | ---- 62 | 63 | NOTE: Selecting an on empty range will cause an exception. 64 | 65 | -------------------------------------------------------------------------------- /doc/reference/main.adoc: -------------------------------------------------------------------------------- 1 | [#main] 2 | == cobalt/main.hpp 3 | 4 | The easiest way to get started with an cobalt application is to use the `co_main` function with the following signature: 5 | 6 | [source,cpp] 7 | ---- 8 | cobalt::main co_main(int argc, char *argv[]); 9 | ---- 10 | 11 | Declaring `co_main` will add a `main` function that performs all the necessary steps to run a coroutine on an event loop. 12 | This allows us to write very simple asynchronous programs. 13 | 14 | [source,cpp] 15 | ---- 16 | cobalt::main co_main(int argc, char *argv[]) 17 | { 18 | auto exec = co_await cobalt::this_coro::executor; // <1> 19 | asio::steady_timer tim{exec, std::chrono::milliseconds(50)}; // <2> 20 | co_await tim.async_wait(cobalt::use_op); // <3> 21 | co_return 0; 22 | } 23 | ---- 24 | <1> get the executor `main` running on 25 | <2> Use it with an asio object 26 | <3> `co_await` an cobalt operation 27 | 28 | The main promise will create an `asio::signal_set` and uses it for cancellation. 29 | `SIGINT` becomes total , while `SIGTERM` becomes terminal cancellation. 30 | 31 | NOTE: The cancellation will not be forwarded to detached coroutines. 32 | The user will need to take care to end then on cancellation, 33 | since the program otherwise doesn't allow graceful termination. 34 | 35 | === Executor 36 | [#main-executor] 37 | 38 | It will also create an `asio::io_context` to run on, which you can get through the `this_coro::executor`. 39 | It will be assigned to the `cobalt::this_thread::get_executor()` . 40 | 41 | === Memory Resource 42 | [#main-allocator] 43 | 44 | It also creates a memory resource that will be used as a default for internal memory allocations. 45 | It will be assigned to the `thread_local` to the `cobalt::this_thread::get_default_resource()`. 46 | 47 | [#main-promise] 48 | === Promise 49 | 50 | Every coroutine has an internal state, called `promise` (not to be confused with the `cobalt::promise`). 51 | Depending on the coroutine properties different things can be `co_await`-ed, like we used in the example above. 52 | 53 | They are implemented through inheritance, and shared among different promise types 54 | 55 | The main promise has the following properties. 56 | 57 | - <> 58 | - <> 59 | - <> 60 | - <> 61 | - <> 62 | 63 | 64 | === Specification 65 | 66 | . declaring `co_main` will implicitly declare a `main` function 67 | . `main` is only present when `co_main` is defined. 68 | . `SIGINT` and `SIGTERM` will cause cancellation of the internal task. 69 | 70 | -------------------------------------------------------------------------------- /doc/reference/op.adoc: -------------------------------------------------------------------------------- 1 | [#cobalt_operation] 2 | == cobalt/op.hpp 3 | 4 | An operation in `cobalt` is an <> wrapping an `asio` operation. 5 | 6 | [#use_op] 7 | === use_op 8 | 9 | The `use_op` token is the direct to create an op, 10 | i.e. using `cobalt::use_op` as the completion token will create the required awaitable. 11 | 12 | [source,cpp] 13 | ---- 14 | auto tim = cobalt::use_op.as_default_on(asio::steady_timer{co_await cobalt::this_coro::executor}); 15 | co_await tim.async_wait(); 16 | ---- 17 | 18 | Depending on the completion signature the `co_await` expression may throw. 19 | 20 | [cols="1,1,1"] 21 | |=== 22 | | Signature | Return type | Exception 23 | 24 | | `void()` | `void` | `noexcept` 25 | | `void(T)` | `T` | `noexcept` 26 | | `void(T...)` | `std::tuple` | `noexcept` 27 | | `void(system::error_code, T)` | `T` | `system::system_error` 28 | | `void(system::error_code, T...)` | `std::tuple` | `system::system_error` 29 | | `void(std::exception_ptr, T)` | `T` | _any exception_ 30 | | `void(std::exception_ptr, T...)` | `std::tuple` | _any exception_ 31 | |=== 32 | 33 | NOTE: `use_op` will never complete immediately, i.e. `await_ready` will always return false, but always suspend the coroutine. 34 | 35 | 36 | 37 | [#op] 38 | === Hand coded Operations 39 | 40 | Operations are a more advanced implementation of the <> feature. 41 | 42 | This library makes it easy to create asynchronous operations with an early completion condition, 43 | i.e. a condition that avoids suspension of coroutines altogether. 44 | 45 | We can for example create a `wait_op` that does nothing if the timer is already expired. 46 | 47 | [source,cpp] 48 | ---- 49 | struct wait_op : cobalt::op // <1> 50 | { 51 | asio::steady_timer & tim; 52 | 53 | wait_op(asio::steady_timer & tim) : tim(tim) {} 54 | 55 | bool ready(cobalt::handler ) // <2> 56 | { 57 | if (tim.expiry() < std::chrono::steady_clock::now()) 58 | h(system::error_code{}); 59 | } 60 | void initiate(cobalt::completion_handler complete) // <3> 61 | { 62 | tim.async_wait(std::move(complete)); 63 | } 64 | }; 65 | ---- 66 | <1> Inherit `op` with the matching signature `await_transform` picks it up 67 | <2> Check if the operation is ready - called from `await_ready` 68 | <3> Initiate the operation if its not ready. 69 | 70 | -------------------------------------------------------------------------------- /doc/reference/promise.adoc: -------------------------------------------------------------------------------- 1 | [#promise] 2 | == cobalt/promise.hpp 3 | 4 | A promise is an eager coroutine that can `co_await` and `co_return` values. That is, it cannot use `co_yield`. 5 | 6 | [source,cpp] 7 | ---- 8 | cobalt::promise delay(std::chrono::milliseconds ms) 9 | { 10 | asio::steady_timer tim{co_await cobalt::this_coro::executor, ms}; 11 | co_await tim.async_wait(cobalt::use_op); 12 | } 13 | 14 | cobalt::main co_main(int argc, char *argv[]) 15 | { 16 | co_await delay(std::chrono::milliseconds(50)); 17 | co_return 0; 18 | } 19 | ---- 20 | 21 | Promises are by default attached. 22 | This means, that a cancellation is sent when the `promise` handles goes out of scope. 23 | 24 | A promise can be detached by calling `detach` or by using the prefix `+` operator. 25 | This is a runtime alternative to using <>. 26 | A detached promise will not send a cancellation on destruction. 27 | 28 | [source,cpp] 29 | ---- 30 | cobalt::promise my_task(); 31 | 32 | cobalt::main co_main(int argc, char *argv[]) 33 | { 34 | +my_task(); // <1> 35 | co_await delay(std::chrono::milliseconds(50)); 36 | co_return 0; 37 | } 38 | ---- 39 | <1> By using `+` the task gets detached. Without it, the compiler would generate a `nodiscard` warning. 40 | 41 | === Executor 42 | [#promise-executor] 43 | 44 | The executor is taken from the `thread_local` <> function, unless a `asio::executor_arg` is used 45 | in any position followed by the executor argument. 46 | 47 | [source, cpp] 48 | ---- 49 | cobalt::promise my_gen(asio::executor_arg_t, asio::io_context::executor_type exec_to_use); 50 | ---- 51 | 52 | === Memory Resource 53 | [#promise-allocator] 54 | 55 | The memory resource is taken from the `thread_local` <> function, 56 | unless a `std::allocator_arg` is used in any position followed by a `polymorphic_allocator` argument. 57 | 58 | [source, cpp] 59 | ---- 60 | cobalt::promise my_gen(std::allocator_arg_t, pmr::polymorphic_allocator alloc); 61 | ---- 62 | 63 | [#promise-outline] 64 | === Outline 65 | 66 | 67 | [source,cpp] 68 | ---- 69 | include::../../include/boost/cobalt/promise.hpp[tag=outline] 70 | ---- 71 | <1> Supports <> 72 | <2> This allows to create promise running in parallel with a simple `+my_task()` expression. 73 | <3> This allows code like `while (p) co_await p;` 74 | 75 | [#promise-promise] 76 | === Promise 77 | 78 | The coroutine promise (`promise::promise_type`) has the following properties. 79 | 80 | - <> 81 | - <> 82 | - <> 83 | - <> 84 | - <> 85 | - <> 86 | - <> 87 | -------------------------------------------------------------------------------- /doc/reference/result.adoc: -------------------------------------------------------------------------------- 1 | [#result] 2 | == cobalt/result.hpp 3 | 4 | Awaitables can be modified to return `system::result` or 5 | `std::tuple` instead of using exceptions. 6 | 7 | [source,cpp] 8 | ---- 9 | // value only 10 | T res = co_await foo(); 11 | 12 | // as result 13 | system::result res = co_await cobalt::as_result(foo()); 14 | 15 | // as tuple 16 | std::tuple res = co_await cobalt::as_tuple(foo()); 17 | ---- 18 | 19 | 20 | Awaitables can also provide custom ways to handle results and tuples, 21 | by providing `await_resume` overloads using `cobalt::as_result_tag` and `cobalt::as_tuple_tag`.: 22 | 23 | [source,cpp, subs="+quotes"] 24 | ---- 25 | __your_result_type__ await_resume(cobalt::as_result_tag); 26 | __your_tuple_type__ await_resume(cobalt::as_tuple_tag); 27 | ---- 28 | 29 | This allows an awaitable to provide other error types than `std::exception_ptr`, 30 | for example `system::error_code`. This is done by <> and <>. 31 | 32 | [source,cpp] 33 | ---- 34 | // example of an op with result system::error_code, std::size_t 35 | system::result await_resume(cobalt::as_result_tag); 36 | std::tuple await_resume(cobalt::as_tuple_tag); 37 | ---- 38 | 39 | NOTE: Awaitables are still allowed to throw exceptions, e.g. for critical exceptions such as OOM. 40 | 41 | -------------------------------------------------------------------------------- /doc/reference/run.adoc: -------------------------------------------------------------------------------- 1 | [#run] 2 | == cobalt/run.hpp 3 | 4 | The `run` function is similar to <> but running synchronously. 5 | It will internally setup an execution context and the memory resources. 6 | 7 | This can be useful when integrating a piece of cobalt code into a synchronous application. 8 | 9 | [#run-outline] 10 | === Outline 11 | 12 | [source,cpp] 13 | ---- 14 | // Run the task and return it's value or rethrow any exception. 15 | T run(task t); 16 | ---- 17 | 18 | [#run-example] 19 | === Example 20 | 21 | [source,cpp] 22 | ---- 23 | cobalt::task work(); 24 | 25 | int main(int argc, char *argv[]) 26 | { 27 | return run(work()); 28 | } 29 | ---- 30 | 31 | -------------------------------------------------------------------------------- /doc/reference/spawn.adoc: -------------------------------------------------------------------------------- 1 | [#spawn] 2 | == cobalt/spawn.hpp 3 | 4 | The `spawn` functions allow to run <> on an asio `executor`/`execution_context` 5 | and consume the result with a https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio/overview/model/completion_tokens.html[completion token]. 6 | 7 | [source,cpp] 8 | ---- 9 | auto spawn(Context & context, task && t, CompletionToken&& token); 10 | auto spawn(Executor executor, task && t, CompletionToken&& token); 11 | ---- 12 | 13 | Spawn will dispatch its initiation and post the completion. 14 | That makes it safe to use task to run the task on another executor 15 | and consume the result on the current one with <>. 16 | That is, `spawn` can be used to cross threads. 17 | 18 | === Example 19 | 20 | [source,cpp] 21 | ---- 22 | cobalt::task work(); 23 | 24 | int main(int argc, char *argv[]) 25 | { 26 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 27 | auto f = spawn(ctx, work(), asio::use_future); 28 | ctx.run(); 29 | 30 | return f.get(); 31 | } 32 | ---- 33 | 34 | WARNING: The caller needs to make sure that the executor is not running on multiple threads 35 | concurrently, e,g, by using a single-threaded `asio::io_context`. 36 | 37 | 38 | -------------------------------------------------------------------------------- /doc/reference/task.adoc: -------------------------------------------------------------------------------- 1 | [#task] 2 | == cobalt/task.hpp 3 | 4 | A task is a lazy coroutine that can `co_await` and `co_return` values. That is, it cannot use `co_yield`. 5 | 6 | [source,cpp] 7 | ---- 8 | cobalt::task delay(std::chrono::milliseconds ms) 9 | { 10 | asio::steady_timer tim{co_await cobalt::this_coro::executor, ms}; 11 | co_await tim.async_wait(cobalt::use_op); 12 | } 13 | 14 | cobalt::main co_main(int argc, char *argv[]) 15 | { 16 | co_await delay(std::chrono::milliseconds(50)); 17 | co_return 0; 18 | } 19 | ---- 20 | 21 | Unlike a <>, a task can be awaited or spawned on another executor than it was created on. 22 | 23 | === Executor 24 | [#task-executor] 25 | 26 | Since a `task` it lazy, it does not need to have an executor on construction. 27 | It rather attempts to take it from the caller or awaiter if present. 28 | Otherwise, it'll default to the thread_local executor. 29 | 30 | === Memory Resource 31 | [#task-allocator] 32 | 33 | The memory resource is *NOT* taken from the `thread_local` <> function, 34 | but `pmr::get_default_resource(), 35 | unless a `std::allocator_arg` is used in any position followed by a `polymorphic_allocator` argument. 36 | 37 | [source, cpp] 38 | ---- 39 | cobalt::task my_gen(std::allocator_arg_t, pmr::polymorphic_allocator alloc); 40 | ---- 41 | 42 | [#task-outline] 43 | === Outline 44 | 45 | 46 | [source,cpp] 47 | ---- 48 | include::../../include/boost/cobalt/task.hpp[tag=outline] 49 | ---- 50 | 51 | NOTE: Tasks can be used synchronously from a sync function by calling `run(my_task())`. 52 | 53 | 54 | [#task-task] 55 | === Promise 56 | 57 | The task promise has the following properties. 58 | 59 | - <> 60 | - <> 61 | - <> 62 | - <> 63 | - <> 64 | - <> 65 | - <> 66 | 67 | [#use_task] 68 | === use_task 69 | 70 | The `use_task` completion token can be used to create a task from an `cobalt_` function. 71 | This is less efficient than <> as it needs to allocate a coroutine frame, 72 | but has a simpler return type and supports <>. 73 | 74 | -------------------------------------------------------------------------------- /doc/reference/this_thread.adoc: -------------------------------------------------------------------------------- 1 | [#this_thread] 2 | == cobalt/this_thread.hpp 3 | 4 | Since everything is single threaded this library provides an executor 5 | & default memory-resource for every thread. 6 | 7 | [source,cpp] 8 | ---- 9 | namespace boost::cobalt::this_thread 10 | { 11 | 12 | pmr::memory_resource* get_default_resource() noexcept; // <1> 13 | pmr::memory_resource* set_default_resource(pmr::memory_resource* r) noexcept; // <2> 14 | pmr::polymorphic_allocator get_allocator(); // <3> 15 | 16 | typename asio::io_context::executor_type & get_executor(); // <4> 17 | void set_executor(asio::io_context::executor_type exec) noexcept; // <5> 18 | 19 | } 20 | ---- 21 | <1> Get the default resource - will be pmr::get_default_resource unless set 22 | <2> Set the default resource - returns the previously set one 23 | <3> Get an allocator wrapping (1) 24 | <4> Get the executor of the thread - throws if not set 25 | <5> Set the executor of the current thread. 26 | 27 | The coroutines will use these as defaults, but keep a copy just in case. 28 | 29 | NOTE: The only exception is the initialization of an cobalt-operation, 30 | which will use the this_thread::executor to rethrow from. -------------------------------------------------------------------------------- /doc/reference/thread.adoc: -------------------------------------------------------------------------------- 1 | [#thread] 2 | == cobalt/thread.hpp 3 | 4 | The thread type is another way to create an environment that is similar to `main`, but doesn't use a `signal_set`. 5 | 6 | [source,cpp] 7 | ---- 8 | cobalt::thread my_thread() 9 | { 10 | auto exec = co_await cobalt::this_coro::executor; // <1> 11 | asio::steady_timer tim{exec, std::chrono::milliseconds(50)}; // <2> 12 | co_await tim.async_wait(cobalt::use_op); // <3> 13 | co_return 0; 14 | } 15 | ---- 16 | <1> get the executor `thread` running on 17 | <2> Use it with an asio object 18 | <3> `co_await` an cobalt operation 19 | 20 | To use a thread you can use it like a `std::thread`: 21 | 22 | [source,cpp] 23 | ---- 24 | int main(int argc, char * argv[]) 25 | { 26 | auto thr = my_thread(); 27 | thr.join(); 28 | return 0; 29 | } 30 | ---- 31 | 32 | A thread is also an `awaitable` (including cancellation). 33 | 34 | [source,cpp] 35 | ---- 36 | cobalt::main co_main(int argc, char * argv[]) 37 | { 38 | auto thr = my_thread(); 39 | co_await thr; 40 | co_return 0; 41 | } 42 | ---- 43 | 44 | NOTE: Destructing a detached thread will cause a hard stop (`io_context::stop`) and join the thread. 45 | 46 | WARNING: Nothing in this library, except for awaiting a <> and <>, is thread-safe. 47 | If you need to transfer data across threads, you'll need a thread-safe utility like https://www.boost.org/doc/libs/master/doc/html/boost_asio/reference/experimental__basic_concurrent_channel.html[`asio::concurrent_channel`]. 48 | You cannot share any cobalt primitives between threads, 49 | with the sole exception of being able to <> a <> onto another thread's executor. 50 | 51 | === Executor 52 | [#thread-executor] 53 | 54 | It will also create an `asio::io_context` to run on, which you can get through the `this_coro::executor`. 55 | It will be assigned to the `cobalt::this_thread::get_executor()` . 56 | 57 | === Memory Resource 58 | [#thread-allocator] 59 | 60 | It also creates a memory resource that will be used as a default for internal memory allocations. 61 | It will be assigned to the `thread_local` to the `cobalt::this_thread::get_default_resource()`. 62 | 63 | [#thread-outline] 64 | === Outline 65 | 66 | 67 | [source,cpp] 68 | ---- 69 | include::../../include/boost/cobalt/thread.hpp[tag=outline] 70 | ---- 71 | <1> Supports <> 72 | <2> Always forward cancel 73 | 74 | [#thread-promise] 75 | === Promise 76 | 77 | The thread promise has the following properties. 78 | 79 | - <> 80 | - <> 81 | - <> 82 | - <> 83 | - <> 84 | - <> 85 | 86 | -------------------------------------------------------------------------------- /doc/reference/wait_group.adoc: -------------------------------------------------------------------------------- 1 | [#wait_group] 2 | == cobalt/wait_group.hpp 3 | 4 | The `wait_group` function can be used to manage 5 | multiple coroutines of type `promise`. 6 | It works out of the box with <>, by having the matching `await_exit` member. 7 | 8 | Essentially, a `wait_group` is a dynamic list of 9 | promises that has a `race` function (`wait_one`), 10 | a `gather` function (`wait_all`) and will clean up on scope exit. 11 | 12 | [source,cpp,subs="+quotes"] 13 | ---- 14 | include::../../include/boost/cobalt/wait_group.hpp[tag=outline] 15 | ---- 16 | 17 | -------------------------------------------------------------------------------- /doc/reference/with.adoc: -------------------------------------------------------------------------------- 1 | [#with] 2 | == cobalt/with.hpp 3 | 4 | The `with` facility provides a way to perform asynchronous tear-down of coroutines. 5 | That is it like an asynchronous destructor call. 6 | 7 | [source,cpp] 8 | ---- 9 | struct my_resource 10 | { 11 | cobalt::promise await_exit(std::exception_ptr e); 12 | }; 13 | 14 | cobalt::promise work(my_resource & res); 15 | 16 | cobalt::promise outer() 17 | { 18 | co_await cobalt::with(my_resource(), &work); 19 | } 20 | ---- 21 | 22 | The teardown can either be done by providing an `await_exit` member function or a `tag_invoke` function 23 | that returns an <> or by providing the teardown as the third argument to `with`. 24 | 25 | [source,cpp] 26 | ---- 27 | using ws_stream = beast::websocket::stream>; 28 | cobalt::promise connect(urls::url); // <1> 29 | cobalt::promise disconnect(ws_stream &ws); // <2> 30 | 31 | auto teardown(const boost::cobalt::with_exit_tag & wet , ws_stream & ws, std::exception_ptr e) 32 | { 33 | return disconnect(ws); 34 | } 35 | 36 | cobalt::promise run_session(ws_stream & ws); 37 | 38 | cobalt::main co_main(int argc, char * argv[]) 39 | { 40 | co_await cobalt::with(co_await connect(argv[1]), &run_session, &teardown); 41 | co_return 0; 42 | } 43 | ---- 44 | <1> Implement websocket connect & websocket initiation 45 | <2> Implement an orderly shutdown. 46 | 47 | NOTE: The `std::exception_ptr` is null if the scope is exited without exception. 48 | NOTE: It's legal for the `exit` functions to take the `exception_ptr` by reference and modify it. -------------------------------------------------------------------------------- /doc/tour/entry.adoc: -------------------------------------------------------------------------------- 1 | == Entry into an cobalt environment 2 | 3 | In order to use <> we need to be able to `co_await` them, i.e. be within a coroutine. 4 | 5 | We got four ways to achieve this: 6 | 7 | 8 | <

>:: replace `int main` with a coroutine 9 | [source,cpp] 10 | ---- 11 | cobalt::main co_main(int argc, char* argv[]) 12 | { 13 | // co_await things here 14 | co_return 0; 15 | } 16 | ---- 17 | 18 | <>:: create a thread for the asynchronous environments 19 | [source,cpp] 20 | ---- 21 | cobalt::thread my_thread() 22 | { 23 | // co_await things here 24 | co_return; 25 | } 26 | 27 | int main(int argc, char ** argv[]) 28 | { 29 | auto t = my_thread(); 30 | t.join(); 31 | return 0; 32 | } 33 | ---- 34 | 35 | <>:: create a task and run or spawn it 36 | [source,cpp] 37 | ---- 38 | cobalt::task my_thread() 39 | { 40 | // co_await things here 41 | co_return; 42 | } 43 | 44 | int main(int argc, char ** argv[]) 45 | { 46 | cobalt::run(my_task()); // sync 47 | asio::io_context ctx; 48 | cobalt::spawn(ctx, my_task(), asio::detached); 49 | ctx.run(); 50 | return 0; 51 | } 52 | ---- 53 | 54 | -------------------------------------------------------------------------------- /doc/tour/generator.adoc: -------------------------------------------------------------------------------- 1 | [#tour-generator] 2 | == Generator 3 | 4 | A <> is the only type in cobalt that can `co_yield` values. 5 | 6 | <> are eager by default. Unlike https://en.cppreference.com/w/cpp/coroutine/generator[std::generator] 7 | the `cobalt::generator` can `co_await` and thus is asynchronous. 8 | 9 | [source,cpp] 10 | ---- 11 | cobalt::generator my_generator() 12 | { 13 | for (int i = 0; i < 10; i++) 14 | co_yield i; 15 | co_return 10; 16 | } 17 | 18 | cobalt::main co_main(int argc, char * argv[]) 19 | { 20 | // create the generator 21 | auto g = my_generator(); 22 | while (g) 23 | printf("Generator %d\n", co_await g); 24 | co_return 0; 25 | } 26 | ---- 27 | 28 | Values can be pushed into the generator, that will be returned from the `co_yield`. 29 | 30 | [source,cpp] 31 | ---- 32 | cobalt::generator my_eager_push_generator(int value) 33 | { 34 | while (value != 0) 35 | value = co_yield value * 0.1; 36 | co_return std::numeric_limits::quiet_NaN(); 37 | } 38 | 39 | cobalt::main co_main(int argc, char * argv[]) 40 | { 41 | // create the generator 42 | auto g = my_generator(5); 43 | 44 | assert(0.5 == co_await g(4)); // result of 5 45 | assert(0.4 == co_await g(3)); // result of 4 46 | assert(0.3 == co_await g(2)); // result of 3 47 | assert(0.2 == co_await g(1)); // result of 2 48 | assert(0.1 == co_await g(0)); // result of 1 49 | 50 | // we let the coroutine go out of scope while suspended 51 | // no need for another co_await of `g` 52 | 53 | co_return 0; 54 | } 55 | ---- 56 | 57 | A coroutine can also be made lazy using <>. 58 | 59 | [source,cpp] 60 | ---- 61 | cobalt::generator my_eager_push_generator() 62 | { 63 | auto value = co_await this_coro::initial; 64 | while (value != 0) 65 | value = co_yield value * 0.1; 66 | co_return std::numeric_limits::quiet_NaN(); 67 | } 68 | 69 | cobalt::main co_main(int argc, char * argv[]) 70 | { 71 | // create the generator 72 | auto g = my_generator(); // lazy, so the generator waits for the first pushed value 73 | assert(0.5 == co_await g(5)); // result of 5 74 | assert(0.4 == co_await g(4)); // result of 4 75 | assert(0.3 == co_await g(3)); // result of 3 76 | assert(0.2 == co_await g(2)); // result of 2 77 | assert(0.1 == co_await g(1)); // result of 1 78 | 79 | // we let the coroutine go out of scope while suspended 80 | // no need for another co_await of `g` 81 | 82 | co_return 0; 83 | } 84 | ---- 85 | 86 | -------------------------------------------------------------------------------- /doc/tour/join.adoc: -------------------------------------------------------------------------------- 1 | [#tour-join] 2 | == join 3 | 4 | If multiple <> work in parallel they can be awaited simultaneously with 5 | <>. 6 | 7 | [source,cpp] 8 | ---- 9 | cobalt::promise some_work(); 10 | cobalt::promise more_work(); 11 | 12 | cobalt::main co_main(int argc, char * argv[]) 13 | { 14 | std::tuple res = cobalt::join(some_work(), more_work()); 15 | co_return 0; 16 | } 17 | ---- 18 | 19 | -------------------------------------------------------------------------------- /doc/tour/promise.adoc: -------------------------------------------------------------------------------- 1 | == Promises 2 | 3 | <> are the recommended default coroutine type. 4 | They're eager and thus easily usable for ad-hoc concurrecy. 5 | 6 | [source,cpp] 7 | ---- 8 | cobalt::promise my_promise() 9 | { 10 | co_await do_the_thing(); 11 | co_return 0; 12 | } 13 | 14 | cobalt::main co_main(int argc, char * argv[]) 15 | { 16 | // start the promise here 17 | auto p = my_promise(); 18 | // do something else here 19 | co_await do_the_other_thing(); 20 | // wait for the promise to complete 21 | auto res = co_await p; 22 | 23 | co_return res; 24 | } 25 | ---- 26 | 27 | -------------------------------------------------------------------------------- /doc/tour/race.adoc: -------------------------------------------------------------------------------- 1 | [#tour-race] 2 | == race 3 | 4 | If multiple <> work in parallel, 5 | but we want to be notified if either completes, we shall use <>. 6 | 7 | [source,cpp] 8 | ---- 9 | cobalt::generator some_data_source(); 10 | cobalt::generator another_data_source(); 11 | 12 | cobalt::main co_main(int argc, char * argv[]) 13 | { 14 | auto g1 = some_data_source(); 15 | auto g2 = another_data_source(); 16 | 17 | int res1 = co_await g1; 18 | double res2 = co_await g2; 19 | 20 | printf("Result: %f", res1 * res2); 21 | 22 | while (g1 && g2) 23 | { 24 | switch(variant2::variant nx = co_await cobalt::race(g1, g2)) 25 | { 26 | case 0: 27 | res1 = variant2::get<0>(nx); 28 | break; 29 | case 1: 30 | res2 = variant2::get<1>(nx); 31 | break; 32 | } 33 | printf("New result: %f", res1 * res2); 34 | } 35 | 36 | co_return 0; 37 | } 38 | ---- 39 | 40 | NOTE: `race` in this context will not cause any data loss. 41 | 42 | -------------------------------------------------------------------------------- /doc/tour/task.adoc: -------------------------------------------------------------------------------- 1 | == Tasks 2 | 3 | <> are lazy, which means they won't do anything before awaited or spwaned. 4 | 5 | [source,cpp] 6 | ---- 7 | cobalt::task my_task() 8 | { 9 | co_await do_the_thing(); 10 | co_return 0; 11 | } 12 | 13 | cobalt::main co_main(int argc, char * argv[]) 14 | { 15 | // create the task here 16 | auto t = my_task(); 17 | // do something else here first 18 | co_await do_the_other_thing(); 19 | // start and wait for the task to complete 20 | auto res = co_await t; 21 | co_return res; 22 | } 23 | ---- 24 | 25 | -------------------------------------------------------------------------------- /doc/tutorial/advanced.adoc: -------------------------------------------------------------------------------- 1 | :example-path: https://github.com/boostorg/cobalt/tree/master/example 2 | 3 | == Advanced examples 4 | 5 | More examples are provided in the repository as code only. All examples are listed below. 6 | 7 | .All examples 8 | [cols="1,5"] 9 | |=== 10 | 11 | |{example-path}/http.cpp[example/http.cpp] 12 | | An http client that performs a single http get request. 13 | 14 | |{example-path}/outcome.cpp[example/outcome.cpp] 15 | | Using the `boost.outcome` coroutine types. 16 | 17 | |{example-path}/python.cpp[example/python.cpp] & {example-path}/python.py[example/python.py] 18 | | Using nanobind to integrate cobalt with python. 19 | It uses python's asyncio as executor and allows C++ to co_await python functions et vice versa. 20 | 21 | |{example-path}/signals.cpp[example/signals.cpp] 22 | | Adopting `boost.signals2` into an awaitable type (single threaded). 23 | 24 | |{example-path}/spsc.cpp[example/spsc.cpp] 25 | | Creating a `boost.lockfree` based & awaitable `spsc_queue` (multi threaded). 26 | 27 | |{example-path}/thread.cpp[example/thread.cpp] 28 | | Using worker threads with `asio`'s `concurrent_channel`. 29 | 30 | |{example-path}/thread_pool.cpp[example/thread_pool.cpp] 31 | | Using an `asio::thread_pool` and spawning <> onto them. 32 | 33 | 34 | |{example-path}/delay.cpp[example/delay.cpp] 35 | |The example used by the <> section 36 | 37 | |{example-path}/delay_op.cpp[example/delay_op.cpp] 38 | |The example used by the <> section 39 | 40 | |{example-path}/echo_server.cpp[example/echo_server.cpp] 41 | |The example used by the <> section 42 | 43 | |{example-path}/ticker.cpp[example/ticker.cpp] 44 | |The example used by the <> section 45 | 46 | |{example-path}/channel.cpp[example/channel.cpp] 47 | |The example used by the <> reference 48 | 49 | 50 | |=== 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /doc/tutorial/delay.adoc: -------------------------------------------------------------------------------- 1 | == delay 2 | Let's start with the simplest example possible: a simple delay. 3 | 4 | .example/delay.cpp 5 | [example] 6 | [source,cpp] 7 | ---- 8 | include::../../example/delay.cpp[tag=timer_example] 9 | ---- 10 | <1> The <> function defines an implicit `main` when used 11 | and is the easiest way to set up an environment to run asynchronous code. 12 | <2> Take the executor from the current coroutine promise. 13 | <3> Use an argument to set the timeout 14 | <4> Perform the wait by using <>. 15 | <5> Return a value that gets returned from the implicit main. 16 | 17 | In this example we use the <
> header, which provides us with a main coroutine if `co_main` 18 | is defined as above. This has a few advantages: 19 | 20 | - The environment get set up correctly (`executor` & `memory`) 21 | - asio is signaled that the context is single threaded 22 | - an `asio::signal_set` with `SIGINT` & `SIGTERM` is automatically connected to cancellations (i.e. `Ctrl+C` causes cancellations) 23 | 24 | This coroutine then has an executor in its promise (the promise the C++ name for a coroutine state. 25 | Not to be confused with <>) which we can obtain through the dummy-<>s in 26 | the <> namespace. 27 | 28 | We can then construct a timer and initiate the `async_wait` with <>. 29 | `cobalt` provides multiple ways to `co_await` to interact with asio, of which <> is the easiest. 30 | 31 | -------------------------------------------------------------------------------- /doc/tutorial/delay_op.adoc: -------------------------------------------------------------------------------- 1 | == delay op 2 | 3 | We've used the `use_op` so far, to use an implicit operation based on asio's completion token mechanic. 4 | 5 | We can however implement our own ops, that can also utilize the `await_ready` optimization. 6 | Unlike immediate completion, the coroutine will never suspend when `await_ready` returns true. 7 | 8 | To leverage this coroutine feature, `cobalt` provides an easy way to create a skipable operation: 9 | 10 | .example/delay_op.cpp 11 | [example] 12 | [source,cpp] 13 | ---- 14 | include::../../example/delay_op.cpp[tag=timer_example] 15 | ---- 16 | <1> Declare the op. We inherit `op` to make it awaitable. 17 | <2> The pre-suspend check is implemented here 18 | <3> Do the wait if we need to 19 | <4> Use the <> just like any other awaitable. 20 | 21 | This way we can minimize the amounts of coroutine suspensions. 22 | 23 | While the above is used with asio, you can also use these handlers 24 | with any other callback based code. 25 | 26 | -------------------------------------------------------------------------------- /doc/tutorial/push_generator.adoc: -------------------------------------------------------------------------------- 1 | == Generator with push value 2 | 3 | Coroutines with push values are not as common, 4 | but can simplify certain issues significantly. 5 | 6 | Since we've already got a json_reader in the previous example, 7 | here's how we can write a json_writer that gets values pushed in. 8 | 9 | The advantage of using a generator is the internal state management. 10 | 11 | [source,cpp] 12 | ---- 13 | cobalt::generator 14 | json_writer(websocket_type & ws) 15 | try 16 | { 17 | char buffer[4096]; 18 | json::serializer ser; 19 | 20 | while (ws.is_open()) // <1> 21 | { 22 | auto val = co_yield system::error_code{}; // <2> 23 | 24 | while (!ser.done()) 25 | { 26 | auto sv = ser.read(buffer); 27 | co_await ws.cobalt_write({sv.data(), sv.size()}); // <3> 28 | } 29 | 30 | } 31 | co_return {}; 32 | } 33 | catch (system::system_error& e) 34 | { 35 | co_return e.code(); 36 | } 37 | catch (std::exception & e) 38 | { 39 | std::cerr << "Error reading: " << e.what() << std::endl; 40 | throw; 41 | } 42 | ---- 43 | <1> Keep running as long as the socket is open 44 | <2> `co_yield` the current error and retrieve a new value. 45 | <3> Write a frame to the websocket 46 | 47 | Now we can use the generator like this: 48 | 49 | [source,cpp] 50 | ---- 51 | auto g = json_writer(my_ws); 52 | 53 | extern std::vector to_write; 54 | 55 | for (auto && tw : std::move(to_write)) 56 | { 57 | if (auto ec = co_await g(std::move(tw))) 58 | return ec; // yield error 59 | } 60 | ---- 61 | 62 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB_RECURSE ALL_EXAMPLES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 3 | 4 | find_package(OpenSSL) 5 | 6 | foreach(SRC ${ALL_EXAMPLES}) 7 | get_filename_component(NAME ${SRC} NAME_WLE ) 8 | # ticker requires 9 | if (NAME STREQUAL ticker) 10 | if (TARGET Boost::json) 11 | add_executable(boost_cobalt_example_${NAME} ${SRC} ) 12 | target_link_libraries(boost_cobalt_example_${NAME} PUBLIC Boost::cobalt Boost::json Boost::url OpenSSL::SSL) 13 | target_compile_definitions(boost_cobalt_example_${NAME} PUBLIC) 14 | endif() 15 | continue() 16 | endif() 17 | 18 | if (NAME STREQUAL python) 19 | find_package(Python 3.8 COMPONENTS Interpreter Development.Module) 20 | if (NOT Python_FOUND) 21 | message(WARNING "Python not found, skipping python example") 22 | continue() 23 | endif() 24 | execute_process( 25 | COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir 26 | OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR) 27 | list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") 28 | find_package(nanobind CONFIG) 29 | if (NOT nanobind_FOUND) 30 | message(WARNING "nanobind not found, skipping python example") 31 | continue() 32 | endif() 33 | nanobind_add_module(boost_cobalt_example_python python.cpp) 34 | target_link_libraries(boost_cobalt_example_python PRIVATE Boost::cobalt) 35 | add_custom_command( 36 | TARGET boost_cobalt_example_python 37 | COMMAND ${CMAKE_COMMAND} -E copy 38 | ${CMAKE_CURRENT_SOURCE_DIR}/python.py 39 | ${CMAKE_CURRENT_BINARY_DIR}/python.py) 40 | continue() 41 | endif() 42 | 43 | add_executable(boost_cobalt_example_${NAME} ${SRC}) 44 | target_link_libraries(boost_cobalt_example_${NAME} PUBLIC Boost::cobalt OpenSSL::SSL) 45 | target_compile_definitions(boost_cobalt_example_${NAME} PUBLIC) 46 | endforeach() 47 | -------------------------------------------------------------------------------- /example/Jamfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Klemens D. Morgenstern 2 | # 3 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | import os ; 8 | import-search /boost/cobalt ; 9 | import boost-cobalt ; 10 | 11 | 12 | project : requirements 13 | BOOST_ASIO_NO_DEPRECATED 14 | msvc:_SCL_SECURE_NO_WARNINGS 15 | msvc:_CRT_SECURE_NO_DEPRECATE 16 | msvc:/bigobj 17 | windows:WIN32_LEAN_AND_MEAN 18 | linux:-lpthread 19 | clang-15:boost-container 20 | clang-14:boost-container 21 | ; 22 | 23 | exe channel : channel.cpp /boost/cobalt//boost_cobalt ; 24 | exe delay : delay.cpp /boost/cobalt//boost_cobalt ; 25 | exe delay_op : delay_op.cpp /boost/cobalt//boost_cobalt ; 26 | exe echo_server : echo_server.cpp /boost/cobalt//boost_cobalt ; 27 | exe outcome : outcome.cpp /boost/cobalt//boost_cobalt /boost/outcome//boost_outcome ; 28 | exe thread : thread.cpp /boost/cobalt//boost_cobalt ; 29 | exe thread_pool : thread_pool.cpp /boost/cobalt//boost_cobalt ; 30 | # exe ticker : ticker.cpp /boost/cobalt//boost_cobalt /boost/json//boost_json ; 31 | -------------------------------------------------------------------------------- /example/channel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace cobalt = boost::cobalt; 14 | 15 | // tag::channel_example[] 16 | cobalt::promise producer(cobalt::channel & chan) 17 | { 18 | for (int i = 0; i < 4; i++) 19 | co_await chan.write(i); 20 | 21 | chan.close(); 22 | } 23 | 24 | cobalt::main co_main(int argc, char * argv[]) 25 | { 26 | cobalt::channel c; 27 | 28 | auto p = producer(c); 29 | while (c.is_open()) 30 | std::cout << co_await c.read() << std::endl; 31 | 32 | co_await p; 33 | co_return 0; 34 | } 35 | // end::channel_example[] 36 | -------------------------------------------------------------------------------- /example/delay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace boost; 11 | 12 | // tag::timer_example[] 13 | cobalt::main co_main(int argc, char * argv[]) // <1> 14 | { 15 | asio::steady_timer tim{co_await asio::this_coro::executor, // <2> 16 | std::chrono::milliseconds(std::stoi(argv[1]))}; // <3> 17 | co_await tim.async_wait(cobalt::use_op); // <4> 18 | co_return 0; // <5> 19 | } 20 | // end::timer_example[] 21 | -------------------------------------------------------------------------------- /example/delay_op.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace boost; 11 | 12 | // tag::timer_example[] 13 | struct wait_op final : cobalt::op // <1> 14 | { 15 | asio::steady_timer & tim; 16 | wait_op(asio::steady_timer & tim) : tim(tim) {} 17 | void ready(cobalt::handler h ) override // <2> 18 | { 19 | if (tim.expiry() < std::chrono::steady_clock::now()) 20 | h(system::error_code{}); 21 | } 22 | void initiate(cobalt::completion_handler complete) override // <3> 23 | { 24 | tim.async_wait(std::move(complete)); 25 | } 26 | }; 27 | 28 | 29 | cobalt::main co_main(int argc, char * argv[]) 30 | { 31 | asio::steady_timer tim{co_await asio::this_coro::executor, 32 | std::chrono::milliseconds(std::stoi(argv[1]))}; 33 | co_await wait_op(tim); // <4> 34 | co_return 0; // 35 | } 36 | // end::timer_example[] 37 | -------------------------------------------------------------------------------- /example/echo_server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // tag::decls[] 19 | namespace cobalt = boost::cobalt; 20 | using boost::asio::ip::tcp; 21 | using boost::asio::detached; 22 | using tcp_acceptor = cobalt::use_op_t::as_default_on_t; 23 | using tcp_socket = cobalt::use_op_t::as_default_on_t; 24 | namespace this_coro = boost::cobalt::this_coro; 25 | //end::decls[] 26 | 27 | // tag::echo[] 28 | cobalt::promise echo(tcp_socket socket) 29 | { 30 | try // <1> 31 | { 32 | char data[4096]; 33 | while (socket.is_open()) // <2> 34 | { 35 | std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data)); // <3> 36 | co_await async_write(socket, boost::asio::buffer(data, n)); // <4> 37 | } 38 | } 39 | catch (std::exception& e) 40 | { 41 | std::printf("echo: exception: %s\n", e.what()); 42 | } 43 | } 44 | // end::echo[] 45 | 46 | 47 | // tag::listen[] 48 | cobalt::generator listen() 49 | { 50 | tcp_acceptor acceptor({co_await cobalt::this_coro::executor}, {tcp::v4(), 55555}); 51 | for (;;) // <1> 52 | { 53 | tcp_socket sock = co_await acceptor.async_accept(); // <2> 54 | co_yield std::move(sock); // <3> 55 | } 56 | co_return tcp_socket{acceptor.get_executor()}; // <4> 57 | } 58 | // end::listen[] 59 | 60 | // tag::run_server[] 61 | cobalt::promise run_server(cobalt::wait_group & workers) 62 | { 63 | auto l = listen(); // <1> 64 | while (true) 65 | { 66 | if (workers.size() == 10u) 67 | co_await workers.wait_one(); // <2> 68 | else 69 | workers.push_back(echo(co_await l)); // <3> 70 | } 71 | } 72 | // end::run_server[] 73 | 74 | // tag::main[] 75 | cobalt::main co_main(int argc, char ** argv) 76 | { 77 | co_await cobalt::with(cobalt::wait_group(), &run_server); // <1> 78 | co_return 0u; 79 | } 80 | // end::main[] 81 | -------------------------------------------------------------------------------- /example/http.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Matthijs Möhlmann 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace cobalt = boost::cobalt; 26 | namespace beast = boost::beast; 27 | 28 | using executor_type = cobalt::use_op_t::executor_with_default; 29 | using socket_type = typename boost::asio::ip::tcp::socket::rebind_executor< 30 | executor_type>::other; 31 | using ssl_socket_type = boost::asio::ssl::stream; 32 | using acceptor_type = typename boost::asio::ip::tcp::acceptor::rebind_executor< 33 | executor_type>::other; 34 | using websocket_type = beast::websocket::stream; 35 | 36 | 37 | cobalt::promise connect(std::string_view host, 38 | boost::asio::ssl::context &ctx) { 39 | boost::asio::ip::tcp::resolver resolve{cobalt::this_thread::get_executor()}; 40 | auto endpoints = co_await resolve.async_resolve(host, "https", cobalt::use_op); 41 | 42 | // Timer for timeouts 43 | 44 | ssl_socket_type sock{cobalt::this_thread::get_executor(), ctx}; 45 | printf("connecting\n"); 46 | 47 | co_await sock.next_layer().async_connect(*endpoints.begin()); 48 | printf("connected\n"); 49 | 50 | // Connected, now do the handshake 51 | printf("handshaking\n"); 52 | co_await sock.async_handshake(boost::asio::ssl::stream_base::client); 53 | printf("hand shook\n"); 54 | co_return sock; 55 | } 56 | 57 | cobalt::main co_main(int argc, char **argv) 58 | { 59 | boost::asio::ssl::context ctx{boost::asio::ssl::context::tls_client}; 60 | auto conn = co_await connect("boost.org", ctx); 61 | printf("connected\n"); 62 | beast::http::request req{beast::http::verb::get, "/index.html", 11}; 63 | req.set(beast::http::field::host, "boost.org"); 64 | co_await beast::http::async_write(conn, req, cobalt::use_op); 65 | 66 | // read the response 67 | beast::flat_buffer b; 68 | beast::http::response response; 69 | co_await beast::http::async_read(conn, b, response, cobalt::use_op); 70 | 71 | // write the response 72 | printf("%s\n", response.body().c_str()); 73 | co_return 0; 74 | } 75 | -------------------------------------------------------------------------------- /example/outcome.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | 11 | using namespace boost; 12 | 13 | outcome_v2::awaitables::lazy lazy_func(int x) 14 | { 15 | co_return x + 1; 16 | } 17 | 18 | outcome_v2::awaitables::eager eager_func(int x) 19 | { 20 | co_return x + 1; 21 | } 22 | 23 | 24 | cobalt::main co_main(int argc, char * argv[]) 25 | { 26 | [[maybe_unused]] auto lr = co_await lazy_func(10); 27 | 28 | assert(lr == 11); 29 | 30 | [[maybe_unused]] auto er = co_await eager_func(10); 31 | assert(er == 11); 32 | 33 | co_return 0; 34 | } 35 | -------------------------------------------------------------------------------- /example/python.py: -------------------------------------------------------------------------------- 1 | # run from the build folder 2 | import asyncio 3 | import boost_cobalt_example_python 4 | 5 | 6 | async def my_cor(): 7 | return "foobar" 8 | 9 | async def use_cpp(): 10 | #test awaiting C++ primitives 11 | 12 | async for item in boost_async_example_python.test_generator(): 13 | print("Cpp generator", item) 14 | 15 | print("Cpp promise gave us", await boost_async_example_python.test_promise()) 16 | 17 | # having C++ await our python coros 18 | await boost_async_example_python.test_py_promise(my_cor()) 19 | 20 | 21 | asyncio.run(use_cpp()) -------------------------------------------------------------------------------- /example/signals.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace cobalt = boost::cobalt; 14 | namespace signals = boost::signals2; 15 | 16 | template 17 | struct signal_awaitable 18 | { 19 | using args_type = boost::callable_traits::args_t; 20 | 21 | 22 | bool await_ready() { return false; } // < always wait for the signal to fire. 23 | void await_suspend(std::coroutine_handle h) 24 | { 25 | awaited_from.reset(h.address()); 26 | // the handler will get copied, so we can't capture the handle with a unique_ptr 27 | signal.connect_extended( 28 | [this, _ = boost::intrusive_ptr(this) 29 | ](const signals::connection & conn, auto ... args) mutable 30 | { 31 | auto aw = std::move(awaited_from); 32 | conn.disconnect(); 33 | result_cache.emplace(std::move(args)...); // the result_catch lives in the coro frame 34 | std::move(aw).resume(); // release ownership & resume 35 | }); 36 | 37 | } 38 | 39 | auto await_resume() // return the value 40 | { 41 | constexpr std::size_t size = std::tuple_size_v; 42 | if constexpr (size == 1u) // single argument doesn't need a tuple 43 | return std::get<0u>(*std::move(result_cache)); 44 | else if constexpr (size > 1u) // make a tuple if more than one arg 45 | return *std::move(result_cache); 46 | // else return void. 47 | } 48 | 49 | // capture it for lazy initialization 50 | Signal & signal; 51 | // capture the ownership of the awaiting coroutine 52 | cobalt::unique_handle awaited_from; 53 | // store the result from the call 54 | std::optional result_cache; 55 | 56 | // to manage shared ownership with an internal counter. 57 | // If the last gets released before the handler is invoked, 58 | // the coro just gets destroyed. 59 | std::size_t use_count{0u}; 60 | friend void intrusive_ptr_add_ref(signal_awaitable * st) {st->use_count++;} 61 | friend void intrusive_ptr_release(signal_awaitable * st) 62 | { 63 | if (st->use_count-- == 1u) 64 | st->awaited_from.reset(); 65 | } 66 | }; 67 | 68 | namespace boost::signals2 69 | { 70 | 71 | // make all signals awaitable 72 | template 73 | auto operator co_await(signals::signal & sig) -> signal_awaitable> 74 | { 75 | return {sig}; 76 | } 77 | 78 | } 79 | 80 | cobalt::promise await_signal(signals::signal & sig) 81 | { 82 | co_return co_await sig; 83 | } 84 | 85 | cobalt::main co_main(int argc, char * argv[]) 86 | { 87 | 88 | signals::signal sig; 89 | auto p = await_signal(sig); 90 | sig(42); 91 | auto res = co_await p; 92 | assert(res == 42); 93 | co_return 0; 94 | } -------------------------------------------------------------------------------- /example/thread.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | /// This example shows how to use threads to offload cpu_intense work. 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | namespace cobalt = boost::cobalt; 17 | using boost::system::error_code; 18 | 19 | template 20 | using cchannel = boost::asio::experimental::concurrent_channel; 21 | 22 | // this is a function doing some CPU heavy work that should be offloaded onto a thread 23 | cobalt::promise cpu_intense_work(int a, int b) {co_return a + b;} 24 | 25 | // this channel is used to send a response to completed work 26 | using response_channel = cchannel; 27 | // this channel is used to send a request to a working thread 28 | using request_channel = cchannel; 29 | 30 | // the worker wrapper 31 | cobalt::thread worker(request_channel & work) 32 | { 33 | while (work.is_open()) 34 | { 35 | auto [ec, a, b, respond_to] = co_await work.async_receive(boost::asio::as_tuple(cobalt::use_op)); 36 | if (ec) // done, ignore. in our code this is only triggered by closing the channel 37 | break; 38 | 39 | // to emulate this being like awaiting on the same thread, we also deliver an exception. 40 | std::exception_ptr ep; 41 | int res = 0; 42 | try 43 | { 44 | res = co_await cpu_intense_work(a, b); 45 | } 46 | catch(...) 47 | { 48 | // this way exception get sent to the awaiting coro as if it was a call. 49 | ep = std::current_exception(); 50 | } 51 | // send the response. If the channel is closed, the program will terminate! 52 | co_await respond_to->async_send(ep, res, boost::asio::redirect_error(cobalt::use_op, ec)); 53 | } 54 | } 55 | 56 | cobalt::promise work(request_channel & rc, int min_a, int max_a, int b) 57 | { 58 | response_channel res{co_await cobalt::this_coro::executor}; 59 | for (int a = min_a; a <= max_a; a++) 60 | { 61 | // the following two calls offload the work to another thread. 62 | co_await rc.async_send(error_code{}, a, b, &res, cobalt::use_op); 63 | int c = co_await res.async_receive(cobalt::use_op); // may throw if working thread has an exception 64 | printf("The CPU intensive result of adding %d to %d, is %d\n", a, b, c); 65 | } 66 | } 67 | 68 | cobalt::main co_main(int argc, char *argv []) 69 | { 70 | // a very simple thread pool 71 | std::vector thrs; 72 | const std::size_t n = 4u; 73 | 74 | request_channel rc{co_await cobalt::this_coro::executor}; 75 | for (auto i = 0u; i < n; i++) 76 | thrs.push_back(worker(rc)); 77 | 78 | try 79 | { 80 | // this is an over simplification, but emulated multiple pieces of 81 | // code in the single threaded environment offloading work to the thread. 82 | co_await cobalt::join( 83 | work(rc, 0, 10, 32), 84 | work(rc, 10, 20, 22), 85 | work(rc, 50, 60, -18) 86 | ); 87 | 88 | } 89 | catch(std::exception & e) 90 | { 91 | printf("Completed with exception %s\n", e.what()); 92 | } 93 | // closing the channel will cause the threads to complete 94 | rc.close(); 95 | // wait them so they don't leak. 96 | co_await cobalt::join(thrs); 97 | co_return 0; 98 | } -------------------------------------------------------------------------------- /example/thread_pool.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | /// This example shows how to use threads to offload cpu_intense work. 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | namespace cobalt = boost::cobalt; 19 | using boost::system::error_code; 20 | 21 | // this is a function doing some CPU heavy work that should be offloaded onto a thread_pool 22 | cobalt::promise cpu_intense_work( 23 | int a, int b, 24 | boost::asio::executor_arg_t = {}, cobalt::executor = cobalt::this_thread::get_executor()) 25 | // ^set the executor manually. but default it so we can still use it with the thread_local one if present 26 | { 27 | co_return a + b; 28 | } 29 | 30 | cobalt::task work(int min_a, int max_a, int b) 31 | { 32 | auto exec = co_await cobalt::this_coro::executor; 33 | for (int a = min_a; a <= max_a; a++) 34 | { 35 | // the following two calls offload the work to another thread. 36 | int c = co_await cpu_intense_work(a, b, boost::asio::executor_arg, exec); 37 | printf("The CPU intensive result of adding %d to %d, is %d\n", a, b, c); 38 | } 39 | } 40 | int main(int , char * []) 41 | { 42 | const std::size_t n = 4u; 43 | boost::asio::thread_pool tp{n}; 44 | 45 | // a very simple thread pool 46 | 47 | auto cpl = 48 | [](std::exception_ptr ep) 49 | { 50 | if (ep) 51 | try 52 | { 53 | std::rethrow_exception(ep); 54 | } 55 | catch(std::exception & e) 56 | { 57 | printf("Completed with exception %s\n", e.what()); 58 | } 59 | }; 60 | 61 | cobalt::spawn(boost::asio::make_strand(tp.get_executor()), work(0, 10, 32), cpl); 62 | cobalt::spawn(boost::asio::make_strand(tp.get_executor()), work(10, 20, 22), cpl); 63 | cobalt::spawn(boost::asio::make_strand(tp.get_executor()), work(50, 60, -18), cpl); 64 | 65 | // wait them so they don't leak. 66 | tp.join(); 67 | return 0; 68 | } -------------------------------------------------------------------------------- /include/boost/cobalt.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_HPP 9 | #define BOOST_COBALT_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #endif //BOOST_COBALT_HPP 34 | 35 | -------------------------------------------------------------------------------- /include/boost/cobalt/async_for.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | #ifndef BOOST_COBALT_COBALT_FOR_HPP 6 | #define BOOST_COBALT_COBALT_FOR_HPP 7 | 8 | #include 9 | 10 | #define BOOST_COBALT_FOR_IMPL(Value, Expression, Id) \ 11 | for (auto && Id = Expression; Id; ) \ 12 | if (Value = co_await Id; false) {} else 13 | 14 | #define BOOST_COBALT_FOR(Value, Expression) \ 15 | BOOST_COBALT_FOR_IMPL(Value, Expression, BOOST_PP_CAT(__boost_cobalt_for_loop_value__, __LINE__)) 16 | 17 | #endif //BOOST_COBALT_COBALT_FOR_HPP 18 | -------------------------------------------------------------------------------- /include/boost/cobalt/concepts.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | #ifndef BOOST_COBALT_CONCEPTS_HPP 6 | #define BOOST_COBALT_CONCEPTS_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace boost::cobalt 19 | { 20 | 21 | // tag::outline[] 22 | template 23 | concept awaitable_type = requires (Awaitable aw, std::coroutine_handle h) 24 | { 25 | {aw.await_ready()} -> std::convertible_to; 26 | {aw.await_suspend(h)}; 27 | {aw.await_resume()}; 28 | }; 29 | 30 | template 31 | concept awaitable = 32 | awaitable_type 33 | || requires (Awaitable && aw) { {std::forward(aw).operator co_await()} -> awaitable_type;} 34 | || requires (Awaitable && aw) { {operator co_await(std::forward(aw))} -> awaitable_type;}; 35 | //end::outline[] 36 | 37 | struct promise_throw_if_cancelled_base; 38 | template 39 | struct enable_awaitables 40 | { 41 | template Aw> 42 | Aw && await_transform(Aw && aw, 43 | const boost::source_location & loc = BOOST_CURRENT_LOCATION) 44 | { 45 | if constexpr (std::derived_from) 46 | { 47 | auto p = static_cast(this); 48 | // a promise inheriting promise_throw_if_cancelled_base needs to also have a .cancelled() function 49 | if (!!p->cancelled() && p->throw_if_cancelled()) 50 | { 51 | constexpr boost::source_location here{BOOST_CURRENT_LOCATION}; 52 | boost::throw_exception(system::system_error( 53 | {asio::error::operation_aborted, &here}, 54 | "throw_if_cancelled"), loc); 55 | } 56 | 57 | } 58 | return static_cast(aw); 59 | } 60 | }; 61 | 62 | template 63 | concept with_get_executor = requires (T& t) 64 | { 65 | {t.get_executor()} -> asio::execution::executor; 66 | }; 67 | 68 | 69 | } 70 | 71 | #endif //BOOST_COBALT_CONCEPTS_HPP 72 | -------------------------------------------------------------------------------- /include/boost/cobalt/config.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | 10 | #ifndef BOOST_COBALT_CONFIG_HPP 11 | #define BOOST_COBALT_CONFIG_HPP 12 | 13 | #if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_COBALT_DYN_LINK) 14 | #if defined(BOOST_COBALT_SOURCE) 15 | #define BOOST_COBALT_DECL BOOST_SYMBOL_EXPORT 16 | #else 17 | #define BOOST_COBALT_DECL BOOST_SYMBOL_IMPORT 18 | #endif 19 | #else 20 | #define BOOST_COBALT_DECL 21 | #endif 22 | 23 | #if defined(BOOST_COBALT_USE_IO_CONTEXT) 24 | # include 25 | #elif !defined(BOOST_COBALT_CUSTOM_EXECUTOR) 26 | # include 27 | #endif 28 | 29 | #if defined(_MSC_VER) 30 | // msvc doesn't correctly suspend for self-deletion, hence we must workaround here 31 | #define BOOST_COBALT_NO_SELF_DELETE 1 32 | #endif 33 | 34 | #if !defined(BOOST_COBALT_USE_STD_PMR) && \ 35 | !defined(BOOST_COBALT_USE_BOOST_CONTAINER_PMR) && \ 36 | !defined(BOOST_COBALT_USE_CUSTOM_PMR) && \ 37 | !defined(BOOST_COBALT_NO_PMR) 38 | #define BOOST_COBALT_USE_STD_PMR 1 39 | #endif 40 | 41 | #if defined(BOOST_COBALT_USE_BOOST_CONTAINER_PMR) 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #endif 49 | 50 | #if defined(BOOST_COBALT_USE_STD_PMR) 51 | #include 52 | #endif 53 | 54 | #if !defined(BOOST_COBALT_OP_SBO_SIZE) 55 | #define BOOST_COBALT_SBO_BUFFER_SIZE 4096 56 | #endif 57 | 58 | namespace boost::cobalt 59 | { 60 | 61 | #if defined(BOOST_COBALT_USE_IO_CONTEXT) 62 | using executor = boost::asio::io_context::executor_type; 63 | #elif !defined(BOOST_COBALT_CUSTOM_EXECUTOR) 64 | using executor = boost::asio::any_io_executor; 65 | #endif 66 | 67 | #if defined(BOOST_COBALT_USE_BOOST_CONTAINER_PMR) 68 | namespace pmr = boost::container::pmr; 69 | #endif 70 | 71 | #if defined(BOOST_COBALT_USE_STD_PMR) 72 | namespace pmr = std::pmr; 73 | #endif 74 | 75 | } 76 | 77 | #endif //BOOST_COBALT_CONFIG_HPP 78 | -------------------------------------------------------------------------------- /include/boost/cobalt/detached.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETACHED_HPP 9 | #define BOOST_COBALT_DETACHED_HPP 10 | 11 | #include 12 | 13 | namespace boost::cobalt 14 | { 15 | 16 | struct detached 17 | { 18 | using promise_type = detail::detached_promise; 19 | }; 20 | 21 | inline detached detail::detached_promise::get_return_object() { return {}; } 22 | 23 | 24 | } 25 | 26 | 27 | #endif //BOOST_COBALT_DETACHED_HPP 28 | -------------------------------------------------------------------------------- /include/boost/cobalt/detail/await_result_helper.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETAIL_AWAIT_RESULT_HELPER_HPP 9 | #define BOOST_COBALT_DETAIL_AWAIT_RESULT_HELPER_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace boost::cobalt::detail 15 | { 16 | 17 | template 18 | auto co_await_result_helper() -> decltype(std::declval()); 19 | 20 | template 21 | auto co_await_result_helper() -> decltype(std::declval().operator co_await()); 22 | 23 | template 24 | auto co_await_result_helper() -> decltype(operator co_await(std::declval())); 25 | 26 | template 27 | using co_awaitable_type = decltype(co_await_result_helper()); 28 | 29 | template 30 | using co_await_result_t = decltype(co_await_result_helper().await_resume()); 31 | 32 | template 33 | T&& get_awaitable_type(T && t) { return std::forward(t);} 34 | 35 | template 36 | requires (requires (T && t) {{operator co_await(std::forward(t))} -> awaitable_type;} ) 37 | decltype(auto) get_awaitable_type(T && t) { return operator co_await(std::forward(t));} 38 | 39 | template 40 | requires (requires (T && t) {{std::forward(t).operator co_await()} -> awaitable_type;} ) 41 | decltype(auto) get_awaitable_type(T && t) { return std::forward(t).operator co_await();} 42 | 43 | template 44 | struct awaitable_type_getter 45 | { 46 | using type = co_awaitable_type; 47 | std::decay_t & ref; 48 | 49 | template 50 | awaitable_type_getter(U && ref) : ref(ref) {} 51 | 52 | operator type () 53 | { 54 | if constexpr (std::is_lvalue_reference_v) 55 | return get_awaitable_type(ref); 56 | else 57 | return get_awaitable_type(std::move(ref)); 58 | } 59 | }; 60 | 61 | 62 | template 63 | struct awaitable_type_getter 64 | { 65 | using type = T&&; 66 | std::decay_t & ref; 67 | 68 | template 69 | awaitable_type_getter(U && ref) : ref(ref) {} 70 | 71 | operator type () 72 | { 73 | if constexpr (std::is_lvalue_reference_v) 74 | return ref; 75 | else 76 | return std::move(ref); 77 | } 78 | }; 79 | 80 | } 81 | 82 | #endif //BOOST_COBALT_DETAIL_AWAIT_RESULT_HELPER_HPP 83 | -------------------------------------------------------------------------------- /include/boost/cobalt/detail/detached.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETAIL_DETACHED_HPP 9 | #define BOOST_COBALT_DETAIL_DETACHED_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace boost::cobalt 27 | { 28 | 29 | struct detached; 30 | 31 | namespace detail 32 | { 33 | 34 | struct detached_promise 35 | : promise_memory_resource_base, 36 | promise_cancellation_base, 37 | promise_throw_if_cancelled_base, 38 | enable_awaitables, 39 | enable_await_allocator, 40 | enable_await_executor, 41 | enable_await_deferred 42 | { 43 | using promise_cancellation_base::await_transform; 44 | using promise_throw_if_cancelled_base::await_transform; 45 | using enable_awaitables::await_transform; 46 | using enable_await_allocator::await_transform; 47 | using enable_await_executor::await_transform; 48 | using enable_await_deferred::await_transform; 49 | 50 | [[nodiscard]] detached get_return_object(); 51 | 52 | std::suspend_never await_transform( 53 | cobalt::this_coro::reset_cancellation_source_t reset) noexcept 54 | { 55 | this->reset_cancellation_source(reset.source); 56 | return {}; 57 | } 58 | 59 | using executor_type = executor; 60 | executor_type exec; 61 | const executor_type & get_executor() const {return exec;} 62 | 63 | template 64 | detached_promise(Args & ...args) 65 | : 66 | #if !defined(BOOST_COBALT_NO_PMR) 67 | promise_memory_resource_base(detail::get_memory_resource_from_args(args...)), 68 | #endif 69 | exec{detail::get_executor_from_args(args...)} 70 | { 71 | } 72 | 73 | std::suspend_never initial_suspend() noexcept {return {};} 74 | std::suspend_never final_suspend() noexcept {return {};} 75 | 76 | void return_void() {} 77 | #if !defined(BOOST_NO_EXCEPTIONS) 78 | void unhandled_exception() { throw ; } 79 | #endif 80 | }; 81 | 82 | } 83 | 84 | } 85 | 86 | #endif //BOOST_COBALT_DETAIL_DETACHED_HPP 87 | -------------------------------------------------------------------------------- /include/boost/cobalt/detail/exception.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETAIL_EXCEPTION_HPP 9 | #define BOOST_COBALT_DETAIL_EXCEPTION_HPP 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace boost::cobalt::detail 17 | { 18 | 19 | BOOST_COBALT_DECL std::exception_ptr moved_from_exception(); 20 | BOOST_COBALT_DECL std::exception_ptr detached_exception(); 21 | BOOST_COBALT_DECL std::exception_ptr completed_unexpected(); 22 | BOOST_COBALT_DECL std::exception_ptr wait_not_ready(); 23 | BOOST_COBALT_DECL std::exception_ptr already_awaited(); 24 | BOOST_COBALT_DECL std::exception_ptr allocation_failed(); 25 | 26 | BOOST_COBALT_DECL BOOST_NORETURN void throw_bad_executor(const boost::source_location & loc = BOOST_CURRENT_LOCATION); 27 | 28 | template 29 | std::exception_ptr wait_not_ready() { return boost::cobalt::detail::wait_not_ready();} 30 | 31 | } 32 | 33 | #endif //BOOST_COBALT_DETAIL_EXCEPTION_HPP 34 | -------------------------------------------------------------------------------- /include/boost/cobalt/detail/forward_cancellation.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETAIL_FORWARD_CANCELLATION_HPP 9 | #define BOOST_COBALT_DETAIL_FORWARD_CANCELLATION_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace boost::cobalt 16 | { 17 | 18 | // Requests cancellation where a successful cancellation results 19 | // in no apparent side effects and where the op can re-awaited. 20 | template 21 | concept interruptible = 22 | ( std::is_rvalue_reference_v && requires (Awaitable && t) {std::move(t).interrupt_await();}) 23 | || (!std::is_rvalue_reference_v && requires (Awaitable t) {t.interrupt_await();}); 24 | 25 | 26 | } 27 | 28 | namespace boost::cobalt::detail 29 | { 30 | 31 | struct forward_cancellation 32 | { 33 | asio::cancellation_signal &cancel_signal; 34 | 35 | forward_cancellation(asio::cancellation_signal &cancel_signal) : cancel_signal(cancel_signal) {} 36 | void operator()(asio::cancellation_type ct) const 37 | { 38 | cancel_signal.emit(ct); 39 | } 40 | }; 41 | 42 | 43 | } 44 | 45 | #endif //BOOST_COBALT_DETAIL_FORWARD_CANCELLATION_HPP 46 | -------------------------------------------------------------------------------- /include/boost/cobalt/detail/this_thread.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETAIL_THIS_THREAD_HPP 9 | #define BOOST_COBALT_DETAIL_THIS_THREAD_HPP 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | namespace boost::cobalt::detail 17 | { 18 | 19 | inline executor 20 | extract_executor(executor exec) { return exec; } 21 | 22 | #if defined(BOOST_COBALT_CUSTOM_EXECUTOR) || defined(BOOST_COBALT_USE_IO_CONTEXT) 23 | BOOST_COBALT_DECL executor 24 | extract_executor(asio::any_io_executor exec); 25 | #endif 26 | 27 | template 28 | executor get_executor_from_args(Args &&... args) 29 | { 30 | using args_type = mp11::mp_list...>; 31 | constexpr static auto I = mp11::mp_find::value; 32 | if constexpr (sizeof...(Args) == I) 33 | return this_thread::get_executor(); 34 | else // 35 | return extract_executor(std::get(std::tie(args...))); 36 | } 37 | 38 | #if !defined(BOOST_COBALT_NO_PMR) 39 | template 40 | pmr::memory_resource * get_memory_resource_from_args(Args &&... args) 41 | { 42 | using args_type = mp11::mp_list...>; 43 | constexpr static auto I = mp11::mp_find::value; 44 | if constexpr (sizeof...(Args) == I) 45 | return this_thread::get_default_resource(); 46 | else // 47 | return std::get(std::tie(args...)).resource(); 48 | } 49 | 50 | template 51 | pmr::memory_resource * get_memory_resource_from_args_global(Args &&... args) 52 | { 53 | using args_type = mp11::mp_list...>; 54 | constexpr static auto I = mp11::mp_find::value; 55 | if constexpr (sizeof...(Args) == I) 56 | return pmr::get_default_resource(); 57 | else // 58 | return std::get(std::tie(args...)).resource(); 59 | } 60 | #endif 61 | 62 | } 63 | 64 | #endif //BOOST_COBALT_DETAIL_THIS_THREAD_HPP 65 | -------------------------------------------------------------------------------- /include/boost/cobalt/detail/wait_group.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_DETAIL_WAIT_GROUP_HPP 9 | #define BOOST_COBALT_DETAIL_WAIT_GROUP_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | 18 | namespace boost::cobalt::detail 19 | { 20 | 21 | struct race_wrapper 22 | { 23 | using impl_type = decltype(race(std::declval> &>())); 24 | std::list> &waitables_; 25 | 26 | race_wrapper(std::list> &waitables) : waitables_(waitables) 27 | { 28 | } 29 | 30 | struct awaitable_type 31 | { 32 | 33 | bool await_ready() 34 | { 35 | if (waitables_.empty()) 36 | return true; 37 | else 38 | return impl_->await_ready(); 39 | } 40 | 41 | template 42 | auto await_suspend(std::coroutine_handle h) 43 | { 44 | return impl_->await_suspend(h); 45 | } 46 | 47 | void await_resume() 48 | { 49 | if (waitables_.empty()) 50 | return; 51 | auto idx = impl_->await_resume(); 52 | if (idx != std::numeric_limits::max()) 53 | waitables_.erase(std::next(waitables_.begin(), idx)); 54 | } 55 | 56 | awaitable_type(std::list> &waitables) : waitables_(waitables) 57 | { 58 | if (!waitables_.empty()) 59 | impl_.emplace(waitables_, random_); 60 | } 61 | 62 | private: 63 | std::optional impl_; 64 | std::list> &waitables_; 65 | std::default_random_engine &random_{detail::prng()}; 66 | 67 | }; 68 | awaitable_type operator co_await() && 69 | { 70 | return awaitable_type(waitables_); 71 | } 72 | }; 73 | 74 | struct gather_wrapper 75 | { 76 | using impl_type = decltype(gather(std::declval> &>())); 77 | std::list> &waitables_; 78 | 79 | gather_wrapper(std::list> &waitables) : waitables_(waitables) 80 | { 81 | } 82 | 83 | struct awaitable_type 84 | { 85 | bool await_ready() 86 | { 87 | if (waitables_.empty()) 88 | return true; 89 | else 90 | return impl_->await_ready(); 91 | } 92 | 93 | template 94 | auto await_suspend(std::coroutine_handle h) 95 | { 96 | return impl_->await_suspend(h); 97 | } 98 | 99 | void await_resume() 100 | { 101 | if (waitables_.empty()) 102 | return; 103 | BOOST_ASSERT(impl_); 104 | impl_->await_resume(); 105 | waitables_.clear(); 106 | } 107 | 108 | awaitable_type(std::list> &waitables) : waitables_(waitables) 109 | { 110 | if (!waitables_.empty()) 111 | impl_.emplace(waitables_); 112 | } 113 | private: 114 | std::list> &waitables_; 115 | std::optional impl_; 116 | }; 117 | 118 | awaitable_type operator co_await() 119 | { 120 | return awaitable_type(waitables_); 121 | } 122 | 123 | }; 124 | 125 | } 126 | 127 | #endif //BOOST_COBALT_DETAIL_WAIT_GROUP_HPP 128 | -------------------------------------------------------------------------------- /include/boost/cobalt/error.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | #ifndef BOOST_COBALT_ERROR_HPP 6 | #define BOOST_COBALT_ERROR_HPP 7 | 8 | #include 9 | #include 10 | 11 | namespace boost::cobalt 12 | { 13 | 14 | enum class error 15 | { 16 | moved_from, 17 | detached, 18 | completed_unexpected, 19 | wait_not_ready, 20 | already_awaited, 21 | allocation_failed 22 | }; 23 | 24 | 25 | struct cobalt_category_t final : system::error_category 26 | { 27 | cobalt_category_t() : system::error_category(0x7d4c7b49d8a4fdull) {} 28 | 29 | 30 | std::string message( int ev ) const override 31 | { 32 | 33 | return message(ev, nullptr, 0u); 34 | } 35 | char const * message( int ev, char * , std::size_t ) const noexcept override 36 | { 37 | switch (static_cast(ev)) 38 | { 39 | case error::moved_from: 40 | return "moved from"; 41 | case error::detached: 42 | return "detached"; 43 | case error::completed_unexpected: 44 | return "completed unexpected"; 45 | case error::wait_not_ready: 46 | return "wait not ready"; 47 | case error::already_awaited: 48 | return "already awaited"; 49 | case error::allocation_failed: 50 | return "allocation failed"; 51 | default: 52 | return "unknown cobalt error"; 53 | } 54 | } 55 | 56 | const char * name() const noexcept override 57 | { 58 | return "boost.cobalt"; 59 | } 60 | }; 61 | 62 | BOOST_COBALT_DECL system::error_category & cobalt_category(); 63 | BOOST_COBALT_DECL system::error_code make_error_code(error e); 64 | 65 | } 66 | 67 | template<> struct boost::system::is_error_code_enum 68 | { 69 | static const bool value = true; 70 | }; 71 | 72 | #endif //BOOST_COBALT_ERROR_HPP 73 | -------------------------------------------------------------------------------- /include/boost/cobalt/experimental/frame.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_EXPERIMENTAL_FRAME_HPP 9 | #define BOOST_COBALT_EXPERIMENTAL_FRAME_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace boost::cobalt::experimental 15 | { 16 | 17 | template 18 | struct frame 19 | { 20 | void (*resume_) (frame *) = +[](frame * ff) { static_cast(ff)->resume();}; 21 | void (*destroy_)(frame *) = +[](frame * ff) { static_cast(ff)->destroy();}; 22 | typedef Promise promise_type; 23 | Promise promise; 24 | 25 | template 26 | frame(Args && ... args) : promise(std::forward(args)...) 27 | { 28 | } 29 | 30 | }; 31 | 32 | 33 | } 34 | 35 | #endif //BOOST_COBALT_EXPERIMENTAL_FRAME_HPP 36 | -------------------------------------------------------------------------------- /include/boost/cobalt/gather.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_GATHER_HPP 9 | #define BOOST_COBALT_GATHER_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace boost::cobalt 15 | { 16 | 17 | 18 | template 19 | auto gather(Promise && ... p) 20 | { 21 | return detail::gather_variadic_impl( 22 | static_cast(p)...); 23 | } 24 | 25 | 26 | template 27 | requires awaitable().begin())>> 28 | auto gather(PromiseRange && p) 29 | { 30 | return detail::gather_ranged_impl{static_cast(p)}; 31 | } 32 | 33 | 34 | 35 | } 36 | 37 | 38 | #endif //BOOST_COBALT_GATHER_HPP 39 | -------------------------------------------------------------------------------- /include/boost/cobalt/join.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_JOIN_HPP 9 | #define BOOST_COBALT_JOIN_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace boost::cobalt 15 | { 16 | 17 | 18 | template 19 | auto join(Promise && ... p) 20 | { 21 | return detail::join_variadic_impl( 22 | static_cast(p)...); 23 | } 24 | 25 | 26 | template 27 | requires awaitable().begin())>> 28 | auto join(PromiseRange && p) 29 | { 30 | return detail::join_ranged_impl{static_cast(p)}; 31 | } 32 | 33 | 34 | 35 | } 36 | 37 | 38 | #endif //BOOST_COBALT_JOIN_HPP 39 | -------------------------------------------------------------------------------- /include/boost/cobalt/main.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | #ifndef BOOST_COBALT_MAIN_HPP 6 | #define BOOST_COBALT_MAIN_HPP 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | 14 | 15 | #include 16 | #include 17 | 18 | namespace boost::cobalt 19 | { 20 | 21 | namespace detail { struct main_promise; } 22 | class main; 23 | 24 | } 25 | 26 | auto co_main(int argc, char * argv[]) -> boost::cobalt::main; 27 | 28 | namespace boost::cobalt 29 | { 30 | 31 | class main 32 | { 33 | detail::main_promise * promise; 34 | main(detail::main_promise * promise) : promise(promise) {} 35 | friend auto ::co_main(int argc, char * argv[]) -> boost::cobalt::main; 36 | friend struct detail::main_promise; 37 | }; 38 | 39 | } 40 | 41 | 42 | 43 | #include 44 | 45 | #endif //BOOST_COBALT_MAIN_HPP 46 | -------------------------------------------------------------------------------- /include/boost/cobalt/noop.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_NOOP_HPP 9 | #define BOOST_COBALT_NOOP_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace boost::cobalt 15 | { 16 | 17 | 18 | // tag::outline[] 19 | // This is a tag type allowing the creation of promises or generators without creating a coroutine. 20 | template 21 | struct noop 22 | { 23 | template 24 | constexpr noop(Args && ... args) noexcept(std::is_nothrow_constructible_v) 25 | : value(std::forward(args)...) 26 | { 27 | } 28 | // end::outline[] 29 | T value; 30 | 31 | constexpr static bool await_ready() {return true;} 32 | template 33 | constexpr static void await_suspend(std::coroutine_handle

) {} 34 | constexpr T await_resume() {return std::move(value);} 35 | 36 | // tag::outline[] 37 | }; 38 | // end::outline[] 39 | 40 | template<> struct noop 41 | { 42 | constexpr static bool await_ready() {return true;} 43 | template 44 | constexpr static void await_suspend(std::coroutine_handle

) {} 45 | constexpr static void await_resume() {} 46 | }; 47 | 48 | 49 | template noop( T &&) -> noop; 50 | template noop(const T & ) -> noop; 51 | noop() -> noop; 52 | 53 | } 54 | 55 | #endif //BOOST_COBALT_NOOP_HPP 56 | -------------------------------------------------------------------------------- /include/boost/cobalt/run.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | #ifndef BOOST_COBALT_RUN_HPP 6 | #define BOOST_COBALT_RUN_HPP 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace boost::cobalt 14 | { 15 | 16 | template 17 | T run(task t) 18 | { 19 | #if !defined(BOOST_COBALT_NO_PMR) 20 | pmr::unsynchronized_pool_resource root_resource{this_thread::get_default_resource()}; 21 | struct reset_res 22 | { 23 | void operator()(pmr::memory_resource * res) 24 | { 25 | this_thread::set_default_resource(res); 26 | } 27 | }; 28 | std::unique_ptr pr{ 29 | boost::cobalt::this_thread::set_default_resource(&root_resource)}; 30 | #endif 31 | std::future f; 32 | { 33 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 34 | struct reset_exec 35 | { 36 | std::optional exec; 37 | 38 | reset_exec() 39 | { 40 | if (this_thread::has_executor()) 41 | exec = this_thread::get_executor(); 42 | } 43 | 44 | ~reset_exec() 45 | { 46 | if (exec) 47 | this_thread::set_executor(*exec); 48 | } 49 | }; 50 | 51 | reset_exec re; 52 | this_thread::set_executor(ctx.get_executor()); 53 | f = spawn(ctx, std::move(t), asio::bind_executor(ctx.get_executor(), asio::use_future)); 54 | ctx.run(); 55 | } 56 | return f.get(); 57 | } 58 | 59 | } 60 | 61 | #endif //BOOST_COBALT_RUN_HPP 62 | -------------------------------------------------------------------------------- /include/boost/cobalt/spawn.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_SPAWN_HPP 9 | #define BOOST_COBALT_SPAWN_HPP 10 | 11 | #include 12 | 13 | namespace boost::cobalt 14 | { 15 | 16 | template 17 | auto spawn(Context & context, 18 | task && t, 19 | CompletionToken&& token) 20 | { 21 | return asio::async_initiate( 22 | detail::async_initiate_spawn{context.get_executor()}, token, std::move(t)); 23 | } 24 | 25 | template Executor, typename T, typename CompletionToken> 26 | auto spawn(Executor executor, task && t, 27 | CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(Executor)) 28 | { 29 | return asio::async_initiate( 30 | detail::async_initiate_spawn{executor}, token, std::move(t)); 31 | } 32 | 33 | template 34 | auto spawn(Context & context, 35 | task && t, 36 | CompletionToken&& token) 37 | { 38 | return asio::async_initiate( 39 | detail::async_initiate_spawn{context.get_executor()}, token, std::move(t)); 40 | } 41 | 42 | template Executor, typename CompletionToken> 43 | auto spawn(Executor executor, task && t, 44 | CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(Executor)) 45 | { 46 | return asio::async_initiate( 47 | detail::async_initiate_spawn{executor}, token, std::move(t)); 48 | 49 | } 50 | 51 | } 52 | 53 | #endif //BOOST_COBALT_SPAWN_HPP 54 | -------------------------------------------------------------------------------- /include/boost/cobalt/this_thread.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_THIS_THREAD_HPP 9 | #define BOOST_COBALT_THIS_THREAD_HPP 10 | 11 | #include 12 | #include 13 | 14 | 15 | #include 16 | 17 | namespace boost::cobalt::this_thread 18 | { 19 | 20 | #if !defined(BOOST_COBALT_NO_PMR) 21 | BOOST_COBALT_DECL pmr::memory_resource* get_default_resource() noexcept; 22 | BOOST_COBALT_DECL pmr::memory_resource* set_default_resource(pmr::memory_resource* r) noexcept; 23 | BOOST_COBALT_DECL pmr::polymorphic_allocator get_allocator(); 24 | #endif 25 | 26 | BOOST_COBALT_DECL 27 | executor & get_executor( 28 | const boost::source_location & loc = BOOST_CURRENT_LOCATION); 29 | BOOST_COBALT_DECL bool has_executor(); 30 | BOOST_COBALT_DECL void set_executor(executor exec) noexcept; 31 | 32 | } 33 | 34 | #endif //BOOST_COBALT_THIS_THREAD_HPP 35 | -------------------------------------------------------------------------------- /include/boost/cobalt/thread.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #ifndef BOOST_COBALT_THREAD_HPP 9 | #define BOOST_COBALT_THREAD_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | 17 | namespace boost::cobalt 18 | { 19 | 20 | // tag::outline[] 21 | struct thread 22 | { 23 | // Send a cancellation signal 24 | void cancel(asio::cancellation_type type = asio::cancellation_type::all); 25 | 26 | 27 | // Allow the thread to be awaited. NOOP if the thread is invalid. 28 | auto operator co_await() &-> detail::thread_awaitable; //<1> 29 | auto operator co_await() && -> detail::thread_awaitable; //<2> 30 | 31 | // Stops the io_context & joins the executor 32 | ~thread(); 33 | /// Move constructible 34 | thread(thread &&) noexcept = default; 35 | 36 | using executor_type = executor; 37 | 38 | using id = std::thread::id; 39 | id get_id() const noexcept; 40 | 41 | // end::outline[] 42 | /* tag::outline[] 43 | // Add the functions similar to `std::thread` 44 | void join(); 45 | bool joinable() const; 46 | void detach(); 47 | 48 | executor_type get_executor() const; 49 | end::outline[] */ 50 | 51 | 52 | BOOST_COBALT_DECL void join(); 53 | BOOST_COBALT_DECL bool joinable() const; 54 | BOOST_COBALT_DECL void detach(); 55 | 56 | executor_type get_executor(const boost::source_location & loc = BOOST_CURRENT_LOCATION) const 57 | { 58 | auto st = state_; 59 | if (!st || st->done) 60 | cobalt::detail::throw_bad_executor(loc); 61 | 62 | return st ->ctx.get_executor(); 63 | } 64 | 65 | 66 | using promise_type = detail::thread_promise; 67 | 68 | private: 69 | thread(std::thread thr, std::shared_ptr state) 70 | : thread_(std::move(thr)), state_(std::move(state)) 71 | { 72 | } 73 | 74 | std::thread thread_; 75 | std::shared_ptr state_; 76 | friend struct detail::thread_promise; 77 | // tag::outline[] 78 | }; 79 | // end::outline[] 80 | 81 | 82 | inline 83 | void thread::cancel(asio::cancellation_type type) 84 | { 85 | if (auto st = state_) 86 | asio::post(state_->ctx, 87 | [s= state_, type] 88 | { 89 | BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__)); 90 | s->signal.emit(type); 91 | }); 92 | } 93 | 94 | 95 | inline 96 | auto thread::operator co_await() &-> detail::thread_awaitable 97 | { 98 | return detail::thread_awaitable{std::move(state_)}; 99 | } 100 | inline 101 | auto thread::operator co_await() && -> detail::thread_awaitable 102 | { 103 | return detail::thread_awaitable{std::move(thread_), std::move(state_)}; 104 | } 105 | inline 106 | thread::~thread() 107 | { 108 | if (state_) 109 | { 110 | state_->ctx.stop(); 111 | state_.reset(); 112 | } 113 | 114 | if (thread_.joinable()) 115 | thread_.join(); 116 | } 117 | 118 | 119 | inline 120 | thread::id thread::get_id() const noexcept {return thread_.get_id();} 121 | 122 | } 123 | 124 | #endif //BOOST_COBALT_THREAD_HPP 125 | -------------------------------------------------------------------------------- /include/boost/cobalt/wait_group.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | #ifndef BOOST_COBALT_WAIT_GROUP_HPP 6 | #define BOOST_COBALT_WAIT_GROUP_HPP 7 | 8 | #include 9 | 10 | namespace boost::cobalt 11 | { 12 | // tag::outline[] 13 | struct wait_group 14 | { 15 | // create a wait_group 16 | explicit 17 | wait_group(asio::cancellation_type normal_cancel = asio::cancellation_type::none, 18 | asio::cancellation_type exception_cancel = asio::cancellation_type::all); 19 | 20 | // insert a task into the group 21 | void push_back(promise p); 22 | 23 | // the number of tasks in the group 24 | std::size_t size() const; 25 | // remove completed tasks without waiting (i.e. zombie tasks) 26 | std::size_t reap(); 27 | // cancel all tasks 28 | void cancel(asio::cancellation_type ct = asio::cancellation_type::all); 29 | // end::outline[] 30 | 31 | /* tag::outline[] 32 | // wait for one task to complete. 33 | __wait_one_op__ wait_one(); 34 | // wait for all tasks to complete 35 | __wait_op__ wait(); 36 | // wait for all tasks to complete 37 | __wait_op__ operator co_await (); 38 | // when used with `with` , this will receive the exception 39 | // and wait for the completion 40 | // if `ep` is set, this will use the `exception_cancel` level, 41 | // otherwise the `normal_cancel` to cancel all promises. 42 | __wait_op__ await_exit(std::exception_ptr ep); 43 | end::outline[] */ 44 | 45 | auto wait_one() -> detail::race_wrapper 46 | { 47 | return detail::race_wrapper(waitables_); 48 | } 49 | 50 | detail::gather_wrapper wait() 51 | { 52 | return detail::gather_wrapper(waitables_); 53 | 54 | } 55 | detail::gather_wrapper::awaitable_type operator co_await () 56 | { 57 | return detail::gather_wrapper(waitables_).operator co_await(); 58 | } 59 | // swallow the exception here. 60 | detail::gather_wrapper await_exit(std::exception_ptr ep) 61 | { 62 | auto ct = ep ? ct_except_ : ct_normal_; 63 | if (ct != asio::cancellation_type::none) 64 | for (auto & w : waitables_) 65 | w.cancel(ct); 66 | return detail::gather_wrapper(waitables_); 67 | } 68 | 69 | 70 | private: 71 | std::list> waitables_; 72 | asio::cancellation_type ct_normal_, ct_except_; 73 | // tag::outline[] 74 | }; 75 | // end::outline[] 76 | 77 | inline wait_group::wait_group( 78 | asio::cancellation_type normal_cancel, 79 | asio::cancellation_type exception_cancel) 80 | : ct_normal_(normal_cancel), ct_except_(exception_cancel) {} 81 | 82 | inline 83 | std::size_t wait_group::size() const {return waitables_.size();} 84 | 85 | inline 86 | std::size_t wait_group::reap() 87 | { 88 | return erase_if(waitables_, [](promise & p) { return p.ready() && p;}); 89 | } 90 | 91 | inline 92 | void wait_group::cancel(asio::cancellation_type ct) 93 | { 94 | for (auto & w : waitables_) 95 | w.cancel(ct); 96 | } 97 | 98 | inline 99 | void wait_group::push_back(promise p) { waitables_.push_back(std::move(p));} 100 | 101 | 102 | } 103 | 104 | #endif //BOOST_COBALT_WAIT_GROUP_HPP 105 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostorg/cobalt/0c6cf566471803dd8a53a5e98511da4516583e88/index.html -------------------------------------------------------------------------------- /meta/libraries.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "cobalt", 3 | "name": "Cobalt", 4 | "authors": [ 5 | "Klemens Morgenstern" 6 | ], 7 | "maintainers": [ 8 | "Klemens Morgenstern " 9 | ], 10 | "description": "Coroutines. Basic Algorithms & Types", 11 | "category": [ 12 | "Concurrent", 13 | "Coroutines", 14 | "Awaitables", 15 | "Asynchronous" 16 | ], 17 | "cxxstd": "20" 18 | } -------------------------------------------------------------------------------- /src/channel.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | 11 | namespace boost::cobalt 12 | { 13 | 14 | channel::~channel() 15 | { 16 | while (!read_queue_.empty()) 17 | read_queue_.front().awaited_from.reset(); 18 | 19 | while (!write_queue_.empty()) 20 | write_queue_.front().awaited_from.reset(); 21 | 22 | } 23 | 24 | void channel::close() 25 | { 26 | is_closed_ = true; 27 | while (!read_queue_.empty()) 28 | { 29 | auto & op = read_queue_.front(); 30 | op.unlink(); 31 | op.cancelled = true; 32 | op.cancel_slot.clear(); 33 | if (op.awaited_from) 34 | asio::defer(executor_, std::move(op.awaited_from)); 35 | } 36 | while (!write_queue_.empty()) 37 | { 38 | auto & op = write_queue_.front(); 39 | op.unlink(); 40 | op.cancelled = true; 41 | op.cancel_slot.clear(); 42 | if (op.awaited_from) 43 | asio::defer(executor_, std::move(op.awaited_from)); 44 | } 45 | } 46 | 47 | system::result channel::read_op::await_resume(const struct as_result_tag &) 48 | { 49 | if (cancel_slot.is_connected()) 50 | cancel_slot.clear(); 51 | 52 | if (cancelled) 53 | return {system::in_place_error, asio::error::operation_aborted}; 54 | 55 | if (!direct) 56 | chn->n_--; 57 | if (!chn->write_queue_.empty()) 58 | { 59 | auto &op = chn->write_queue_.front(); 60 | BOOST_ASSERT(chn->read_queue_.empty()); 61 | if (op.await_ready()) 62 | { 63 | op.unlink(); 64 | BOOST_ASSERT(op.awaited_from); 65 | asio::post(chn->executor_, std::move(op.awaited_from)); 66 | } 67 | } 68 | return {system::in_place_value}; 69 | } 70 | 71 | void channel::read_op::await_resume() 72 | { 73 | await_resume(as_result_tag{}).value(loc); 74 | } 75 | 76 | std::tuple channel::read_op::await_resume(const struct as_tuple_tag & ) 77 | { 78 | return await_resume(as_result_tag{}).error(); 79 | } 80 | 81 | 82 | system::result channel::write_op::await_resume(const struct as_result_tag &) 83 | { 84 | if (cancel_slot.is_connected()) 85 | cancel_slot.clear(); 86 | if (cancelled) 87 | return {system::in_place_error, asio::error::operation_aborted}; 88 | if (!direct) 89 | chn->n_++; 90 | 91 | if (!chn->read_queue_.empty()) 92 | { 93 | auto & op = chn->read_queue_.front(); 94 | BOOST_ASSERT(chn->write_queue_.empty()); 95 | if (op.await_ready()) 96 | { 97 | op.unlink(); 98 | BOOST_ASSERT(op.awaited_from); 99 | asio::post(chn->executor_, std::move(op.awaited_from)); 100 | } 101 | } 102 | return {system::in_place_value}; 103 | } 104 | 105 | 106 | void channel::write_op::await_resume() 107 | { 108 | await_resume(as_result_tag{}).value(loc); 109 | } 110 | 111 | 112 | std::tuple channel::write_op::await_resume(const struct as_tuple_tag & ) 113 | { 114 | return await_resume(as_result_tag{}).error(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/detail/exception.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace boost::cobalt::detail 14 | { 15 | 16 | std::exception_ptr moved_from_exception() 17 | { 18 | static auto ep = std::make_exception_ptr(system::error_code( 19 | error::moved_from 20 | )); 21 | return ep; 22 | } 23 | 24 | std::exception_ptr detached_exception() 25 | { 26 | 27 | static auto ep = std::make_exception_ptr(system::error_code( 28 | error::detached 29 | )); 30 | return ep; 31 | } 32 | 33 | std::exception_ptr completed_unexpected() 34 | { 35 | 36 | static auto ep = std::make_exception_ptr(system::error_code( 37 | error::completed_unexpected 38 | )); 39 | return ep; 40 | } 41 | 42 | std::exception_ptr wait_not_ready() 43 | { 44 | static auto ep = std::make_exception_ptr(system::error_code( 45 | error::wait_not_ready 46 | )); 47 | return ep; 48 | } 49 | 50 | std::exception_ptr already_awaited() 51 | { 52 | static auto ep = std::make_exception_ptr(system::error_code( 53 | error::already_awaited 54 | )); 55 | return ep; 56 | } 57 | 58 | 59 | std::exception_ptr allocation_failed() 60 | { 61 | static auto ep = std::make_exception_ptr(system::error_code( 62 | error::already_awaited 63 | )); 64 | return ep; 65 | } 66 | 67 | void throw_bad_executor(const boost::source_location & loc) 68 | { 69 | #if defined(BOOST_ASIO_NO_TS_EXECUTORS) 70 | boost::throw_exception(boost::asio::execution::bad_executor(), loc); 71 | #else 72 | boost::throw_exception(boost::asio::bad_executor(), loc); 73 | #endif 74 | } 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/detail/util.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace boost::cobalt::detail 15 | { 16 | 17 | #if BOOST_COBALT_NO_SELF_DELETE 18 | 19 | void self_destroy(std::coroutine_handle h, const cobalt::executor & exec) noexcept 20 | { 21 | #if defined(BOOST_COBALT_NO_PMR) 22 | asio::post(exec, [del=unique_handle(h.address())]() mutable {}); 23 | #else 24 | asio::post(exec, 25 | asio::bind_allocator( 26 | this_thread::get_allocator(), 27 | [del=unique_handle(h.address())]() mutable 28 | { 29 | })); 30 | #endif 31 | 32 | } 33 | #endif 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/error.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | namespace boost::cobalt 9 | { 10 | system::error_category & cobalt_category() 11 | { 12 | static cobalt_category_t cat; 13 | return cat; 14 | } 15 | 16 | system::error_code make_error_code(error e) 17 | { 18 | return system::error_code(static_cast(e), cobalt_category()); 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include "boost/cobalt/main.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | namespace boost::cobalt::detail 16 | { 17 | 18 | auto main_promise::final_suspend() noexcept -> std::suspend_never 19 | { 20 | system::error_code ec; 21 | if (signal_set) 22 | signal_set->cancel(ec); 23 | return std::suspend_never(); // enable_yielding_tasks::final_suspend(); 24 | } 25 | 26 | int main_promise::run_main(::boost::cobalt::main mn) 27 | { 28 | asio::io_context ctx{BOOST_ASIO_CONCURRENCY_HINT_1}; 29 | boost::cobalt::this_thread::set_executor(ctx.get_executor()); 30 | int res = -1; 31 | mn.promise->result = &res; 32 | mn.promise->exec.emplace(ctx.get_executor()); 33 | mn.promise->exec_ = mn.promise->exec->get_executor(); 34 | auto p = std::coroutine_handle::from_promise(*mn.promise); 35 | asio::basic_signal_set ss{ctx, SIGINT, SIGTERM}; 36 | mn.promise->signal_set = &ss; 37 | struct work 38 | { 39 | asio::basic_signal_set & ss; 40 | asio::cancellation_signal & signal; 41 | void operator()(system::error_code ec, int sig) const 42 | { 43 | BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__)); 44 | if (sig == SIGINT) 45 | signal.emit(asio::cancellation_type::total); 46 | if (sig == SIGTERM) 47 | signal.emit(asio::cancellation_type::terminal); 48 | if (!ec) 49 | ss.async_wait(*this); 50 | } 51 | }; 52 | 53 | ss.async_wait(work{ss, mn.promise->signal}); 54 | asio::post(ctx.get_executor(), 55 | [p] 56 | { 57 | BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__)); 58 | p.resume(); 59 | }); 60 | 61 | ctx.run(); 62 | return res; 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/this_thread.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | 16 | #include 17 | 18 | namespace boost::cobalt::this_thread 19 | { 20 | 21 | namespace detail 22 | { 23 | #if !defined(BOOST_COBALT_NO_PMR) 24 | thread_local pmr::memory_resource *default_coro_memory_resource = pmr::get_default_resource(); 25 | #endif 26 | 27 | thread_local std::optional executor; 28 | } 29 | 30 | #if !defined(BOOST_COBALT_NO_PMR) 31 | pmr::memory_resource* get_default_resource() noexcept 32 | { 33 | return detail::default_coro_memory_resource; 34 | } 35 | 36 | pmr::memory_resource* set_default_resource(pmr::memory_resource* r) noexcept 37 | { 38 | auto pre = get_default_resource(); 39 | detail::default_coro_memory_resource = r; 40 | return pre; 41 | } 42 | 43 | pmr::polymorphic_allocator get_allocator() 44 | { 45 | return pmr::polymorphic_allocator{get_default_resource()}; 46 | } 47 | #endif 48 | 49 | bool has_executor() 50 | { 51 | return detail::executor.has_value(); 52 | } 53 | 54 | executor & get_executor(const boost::source_location & loc) 55 | { 56 | if (!detail::executor) 57 | cobalt::detail::throw_bad_executor(loc); 58 | 59 | return *detail::executor; 60 | } 61 | 62 | struct this_thread_service : asio::detail::execution_context_service_base 63 | { 64 | this_thread_service(asio::execution_context & ctx) 65 | : asio::detail::execution_context_service_base(ctx) 66 | { 67 | } 68 | 69 | 70 | void shutdown() override 71 | { 72 | 73 | if (detail::executor && (&asio::query(*detail::executor, asio::execution::context) == &this->context())) 74 | detail::executor.reset(); 75 | } 76 | }; 77 | 78 | void set_executor(executor exec) noexcept 79 | { 80 | detail::executor = std::move(exec); 81 | asio::use_service(asio::query(*detail::executor, asio::execution::context)); 82 | } 83 | } 84 | 85 | namespace boost::cobalt::detail 86 | { 87 | 88 | #if defined(BOOST_COBALT_CUSTOM_EXECUTOR) || defined(BOOST_COBALT_USE_IO_CONTEXT) 89 | executor 90 | extract_executor(asio::any_io_executor exec) 91 | { 92 | auto t = exec.target(); 93 | if (t == nullptr) 94 | cobalt::detail::throw_bad_executor(); 95 | return *t; 96 | } 97 | #endif 98 | } 99 | -------------------------------------------------------------------------------- /src/thread.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace boost::cobalt 16 | { 17 | 18 | namespace detail 19 | { 20 | 21 | thread_promise::thread_promise() 22 | : promise_cancellation_base( 23 | signal_helper_2::signal.slot(), asio::enable_total_cancellation()) 24 | { 25 | mtx.lock(); 26 | } 27 | 28 | void run_thread( 29 | std::shared_ptr st_, 30 | unique_handle h) 31 | { 32 | 33 | #if !defined(BOOST_COBALT_NO_PMR) 34 | pmr::unsynchronized_pool_resource resource; 35 | boost::cobalt::this_thread::set_default_resource(&resource); 36 | h->resource = &resource; 37 | #endif 38 | 39 | { 40 | auto st = std::move(st_); 41 | h->reset_cancellation_source(st->signal.slot()); 42 | h->set_executor(st->ctx.get_executor()); 43 | boost::cobalt::this_thread::set_executor(st->ctx.get_executor()); 44 | 45 | asio::post( 46 | st->ctx.get_executor(), 47 | [st, h = std::move(h)]() mutable 48 | { 49 | std::lock_guard lock{h->mtx}; 50 | std::move(h).resume(); 51 | }); 52 | 53 | std::exception_ptr ep; 54 | 55 | BOOST_TRY 56 | { 57 | st->ctx.run(); 58 | } 59 | BOOST_CATCH(...) 60 | { 61 | ep = std::current_exception(); 62 | } 63 | BOOST_CATCH_END 64 | 65 | st->done = true; 66 | st->signal.slot().clear(); 67 | std::lock_guard lock(st->mtx); 68 | if (!st->waitor && ep) // nobodies waiting, so unhandled exception 69 | std::rethrow_exception(ep); 70 | else if (st->waitor) 71 | asio::post(asio::append(*std::exchange(st->waitor, std::nullopt), ep)); 72 | } 73 | } 74 | 75 | 76 | boost::cobalt::thread detail::thread_promise::get_return_object() 77 | { 78 | auto st = std::make_shared(); 79 | boost::cobalt::thread res{std::thread{ 80 | [st, 81 | h = unique_handle::from_promise(*this)]() mutable 82 | { 83 | run_thread(std::move(st), std::move(h)); 84 | } 85 | }, st 86 | }; 87 | 88 | return res; 89 | } 90 | 91 | 92 | } 93 | 94 | void thread::join() {thread_.join();} 95 | bool thread::joinable() const {return thread_.joinable();} 96 | void thread::detach() 97 | { 98 | thread_.detach(); 99 | state_ = nullptr; 100 | } 101 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(NOT TARGET tests) 2 | add_custom_target(tests) 3 | set_property(TARGET tests PROPERTY FOLDER _deps) 4 | endif() 5 | 6 | add_library(boost_cobalt_static_tests EXCLUDE_FROM_ALL concepts.cpp util.cpp) 7 | target_link_libraries(boost_cobalt_static_tests Boost::cobalt) 8 | 9 | add_executable(boost_cobalt_main EXCLUDE_FROM_ALL main.cpp) 10 | add_executable(boost_cobalt_main_compile EXCLUDE_FROM_ALL main_compile.cpp) 11 | add_executable(boost_cobalt_basic_tests EXCLUDE_FROM_ALL 12 | async_for.cpp test_main.cpp promise.cpp with.cpp op.cpp handler.cpp join.cpp race.cpp this_coro.cpp 13 | channel.cpp generator.cpp run.cpp task.cpp gather.cpp wait_group.cpp wrappers.cpp left_race.cpp 14 | strand.cpp fork.cpp thread.cpp any_completion_handler.cpp detached.cpp monotonic_resource.cpp sbo_resource.cpp) 15 | 16 | target_link_libraries(boost_cobalt_main Boost::cobalt) 17 | target_link_libraries(boost_cobalt_main_compile Boost::cobalt) 18 | target_link_libraries(boost_cobalt_basic_tests Boost::cobalt Boost::unit_test_framework) 19 | 20 | add_test(NAME boost_cobalt_main COMMAND boost_cobalt_main) 21 | add_test(NAME boost_cobalt_basic_tests COMMAND boost_cobalt_basic_tests) 22 | 23 | add_executable(boost_cobalt_experimental EXCLUDE_FROM_ALL test_main.cpp experimental/context.cpp experimental/yield_context.cpp experimental/composition.cpp) 24 | target_link_libraries(boost_cobalt_experimental Boost::cobalt Boost::unit_test_framework Boost::context) 25 | add_test(NAME boost_cobalt_experimental COMMAND boost_cobalt_experimental) 26 | 27 | add_dependencies(tests boost_cobalt_main boost_cobalt_basic_tests boost_cobalt_static_tests boost_cobalt_experimental) 28 | -------------------------------------------------------------------------------- /test/Jamfile.jam: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Klemens D. Morgenstern 2 | # 3 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | 7 | import os ; 8 | import-search /boost/cobalt ; 9 | import boost-cobalt ; 10 | 11 | 12 | project : requirements 13 | BOOST_ASIO_NO_DEPRECATED 14 | msvc:_SCL_SECURE_NO_WARNINGS 15 | msvc:_CRT_SECURE_NO_DEPRECATE 16 | msvc:/bigobj 17 | windows:WIN32_LEAN_AND_MEAN 18 | linux:-lpthread 19 | clang-15:boost-container 20 | clang-14:boost-container 21 | ; 22 | 23 | import testing ; 24 | 25 | lib test_impl : test_main.cpp /boost/cobalt//boost_cobalt /boost/test//boost_unit_test_framework : 26 | static 27 | ; 28 | 29 | run main.cpp /boost/cobalt//boost_cobalt ; 30 | run main_compile.cpp /boost/cobalt//boost_cobalt util.cpp concepts.cpp ; 31 | 32 | for local src in [ glob *.cpp : main.cpp main_compile.cpp test_main.cpp concepts.cpp util.cpp ] 33 | { 34 | run $(src) test_impl ; 35 | } 36 | 37 | run experimental/context.cpp experimental/composition.cpp test_impl //boost/context ; -------------------------------------------------------------------------------- /test/any_completion_handler.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "boost/cobalt/main.hpp" 18 | #include "boost/cobalt/op.hpp" 19 | 20 | #include "test.hpp" 21 | 22 | namespace cobalt = boost::cobalt; 23 | 24 | BOOST_AUTO_TEST_SUITE(any_completion_token); 25 | 26 | void cobalt_sleep_impl( 27 | boost::asio::any_completion_handler handler, 28 | boost::asio::any_io_executor ex, 29 | std::chrono::nanoseconds duration 30 | ) 31 | { 32 | auto timer = std::make_shared(ex, duration); 33 | timer->async_wait(boost::asio::consign(std::move(handler), timer)); 34 | } 35 | 36 | template 37 | inline auto cobalt_sleep( 38 | boost::asio::any_io_executor ex, 39 | std::chrono::nanoseconds duration, 40 | CompletionToken&& token 41 | ) 42 | { 43 | return boost::asio::async_initiate( 44 | cobalt_sleep_impl, 45 | token, 46 | std::move(ex), 47 | duration 48 | ); 49 | } 50 | 51 | 52 | CO_TEST_CASE(sleep_any_cpl_token) 53 | { 54 | co_await cobalt_sleep(co_await cobalt::this_coro::executor, std::chrono::milliseconds(1), cobalt::use_op); 55 | } 56 | 57 | BOOST_AUTO_TEST_SUITE_END(); 58 | -------------------------------------------------------------------------------- /test/async_for.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include "test.hpp" 15 | 16 | using namespace boost; 17 | 18 | std::array test_data = {1,2,3,4,5,6,7,8,9,0}; 19 | 20 | cobalt::generator test_data_gen() 21 | { 22 | for (auto & td : test_data) 23 | { 24 | if (&td == &test_data.back()) 25 | co_return td; 26 | else 27 | co_yield td; 28 | } 29 | co_return -1; 30 | } 31 | 32 | cobalt::generator once_gen() 33 | { 34 | co_return 0; 35 | } 36 | 37 | cobalt::generator throw_gen() 38 | { 39 | co_yield 42; 40 | throw std::runtime_error("throw_gen"); 41 | co_return 0; 42 | } 43 | 44 | BOOST_AUTO_TEST_SUITE(async_for); 45 | 46 | /// If the awaitable is not empty the loop must be entered for every value exactly once 47 | CO_TEST_CASE(empty_awaitable) 48 | { 49 | auto tg = once_gen(); 50 | co_await tg; 51 | 52 | /// also[lvalue]: The iterated expression can be an lvalue 53 | BOOST_COBALT_FOR(auto i, tg) 54 | { 55 | BOOST_CHECK(false); 56 | boost::ignore_unused(i); 57 | } 58 | } 59 | 60 | /// If the awaitable is not empty the loop must be entered for every value exactly once 61 | CO_TEST_CASE(regular) 62 | { 63 | auto itr = test_data.begin(); 64 | /// also[rvalue]: The iterated expression can be an rvalue 65 | BOOST_COBALT_FOR(auto i, test_data_gen()) 66 | { 67 | BOOST_CHECK(i == *itr++); 68 | } 69 | } 70 | 71 | /// Any exception thrown from the co_await must be propagated. 72 | CO_TEST_CASE(exception) 73 | { 74 | auto inner = []() -> cobalt::generator 75 | { 76 | BOOST_COBALT_FOR(auto i, throw_gen()) boost::ignore_unused(i); // should throw 77 | co_return -1; 78 | }; 79 | try { BOOST_CHECK_THROW(co_await inner(), boost::system::system_error); } catch(...) {} 80 | } 81 | 82 | BOOST_AUTO_TEST_SUITE_END(); -------------------------------------------------------------------------------- /test/cmake_install_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2021 Peter Dimov 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # https://www.boost.org/LICENSE_1_0.txt 4 | 5 | cmake_minimum_required(VERSION 3.5...3.20) 6 | 7 | project(cmake_install_test LANGUAGES CXX) 8 | 9 | find_package(boost_cobalt REQUIRED) 10 | 11 | add_executable(main main.cpp) 12 | target_link_libraries(main Boost::cobalt) 13 | 14 | enable_testing() 15 | add_test(main main) 16 | -------------------------------------------------------------------------------- /test/cmake_install_test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | boost::cobalt::main co_main(int argc, char *argv[]) 12 | { 13 | boost::asio::steady_timer tim{co_await boost::asio::this_coro::executor, std::chrono::milliseconds(50)}; 14 | co_await tim.async_wait(boost::cobalt::use_op); 15 | 16 | co_return 0; 17 | } 18 | -------------------------------------------------------------------------------- /test/cmake_subdir_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2021 Peter Dimov 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | cmake_minimum_required(VERSION 3.5...3.20) 6 | 7 | project(cmake_subdir_test LANGUAGES CXX) 8 | 9 | set(BOOST_INCLUDE_LIBRARIES cobalt) 10 | add_subdirectory(../../../.. boostorg/boost) 11 | 12 | add_executable(main main.cpp) 13 | target_link_libraries(main Boost::cobalt) 14 | 15 | enable_testing() 16 | add_test(main main) 17 | -------------------------------------------------------------------------------- /test/cmake_subdir_test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | boost::cobalt::main co_main(int argc, char *argv[]) 12 | { 13 | boost::asio::steady_timer tim{co_await boost::asio::this_coro::executor, std::chrono::milliseconds(50)}; 14 | co_await tim.async_wait(boost::cobalt::use_op); 15 | 16 | co_return 0; 17 | } 18 | -------------------------------------------------------------------------------- /test/concepts.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | 9 | struct foo_aw 10 | { 11 | bool await_ready(); 12 | bool await_suspend(std::coroutine_handle); 13 | void await_resume(); 14 | }; 15 | 16 | struct foo 17 | { 18 | foo_aw operator co_await() const; 19 | }; 20 | 21 | 22 | struct my_promise ; 23 | 24 | 25 | struct special_aw 26 | { 27 | bool await_ready(); 28 | bool await_suspend(std::coroutine_handle); 29 | void await_resume(); 30 | }; 31 | 32 | 33 | 34 | static_assert(boost::cobalt::awaitable_type); 35 | static_assert(boost::cobalt::awaitable_type); 36 | static_assert(!boost::cobalt::awaitable_type); 37 | 38 | static_assert(boost::cobalt::awaitable); 39 | static_assert(boost::cobalt::awaitable); 40 | static_assert(boost::cobalt::awaitable); 41 | 42 | static_assert(boost::cobalt::awaitable); 43 | static_assert(boost::cobalt::awaitable); 44 | static_assert(boost::cobalt::awaitable); 45 | 46 | static_assert(!boost::cobalt::awaitable); 47 | static_assert(!boost::cobalt::awaitable); 48 | static_assert(boost::cobalt::awaitable); -------------------------------------------------------------------------------- /test/detached.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | #include 9 | 10 | using namespace boost; 11 | 12 | #include "test.hpp" 13 | 14 | BOOST_AUTO_TEST_SUITE(detached); 15 | 16 | cobalt::detached d(bool & done) 17 | { 18 | asio::steady_timer tim{co_await cobalt::this_coro::executor, std::chrono::milliseconds(10)}; 19 | BOOST_CHECK_NO_THROW(co_await tim.async_wait(cobalt::use_op)); 20 | done = true; 21 | } 22 | 23 | BOOST_AUTO_TEST_CASE(detached) 24 | { 25 | asio::io_context ctx; 26 | cobalt::this_thread::set_executor(ctx.get_executor()); 27 | 28 | bool done = false; 29 | d(done); 30 | 31 | BOOST_CHECK(!done); 32 | ctx.run(); 33 | BOOST_CHECK(done); 34 | } 35 | 36 | BOOST_AUTO_TEST_SUITE_END(); -------------------------------------------------------------------------------- /test/experimental/composition.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | 10 | #include "../test.hpp" 11 | 12 | using namespace boost::cobalt; 13 | 14 | struct dummy_op final : op 15 | { 16 | void initiate(completion_handler) 17 | { 18 | std::tuple<> t = co_await boost::asio::post(co_await this_coro::executor, boost::asio::deferred); 19 | t = co_await boost::asio::post(co_await this_coro::executor, use_op); 20 | co_return {boost::asio::error::no_such_device, 42}; 21 | } 22 | }; 23 | 24 | CO_TEST_CASE(composition) 25 | { 26 | auto [ec, n] = co_await boost::cobalt::as_tuple(dummy_op{}); 27 | BOOST_CHECK(ec == boost::asio::error::no_such_device); 28 | BOOST_CHECK(n == 42); 29 | co_return ; 30 | } 31 | -------------------------------------------------------------------------------- /test/experimental/context.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../test.hpp" 12 | 13 | using namespace boost::cobalt; 14 | 15 | BOOST_AUTO_TEST_SUITE(context_); 16 | 17 | BOOST_AUTO_TEST_CASE(basics) 18 | { 19 | boost::cobalt::experimental::detail::context_frame ff; 20 | 21 | auto hh = std::coroutine_handle::from_address(&ff); 22 | BOOST_CHECK(!hh.done()); 23 | ff.resume_ = nullptr; 24 | BOOST_CHECK(hh.done()); 25 | 26 | BOOST_CHECK(&ff.promise == &hh.promise()); 27 | BOOST_CHECK(std::coroutine_handle::from_promise(ff.promise).address() == &ff); 28 | } 29 | 30 | int stackful_task(experimental::context> c, int init) 31 | { 32 | static_assert(experimental::detail::has_await_transform); 33 | BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); 34 | 35 | bool done = false; 36 | boost::asio::post(c.promise().get_executor(), 37 | [&]{done = true;}); 38 | 39 | BOOST_CHECK(!done); 40 | c.await(boost::asio::post(use_op)); 41 | BOOST_CHECK(done); 42 | return init * 2; 43 | } 44 | 45 | CO_TEST_CASE(task_) 46 | { 47 | task t = experimental::make_context(&stackful_task, 12); 48 | 49 | BOOST_CHECK(24 == co_await t); 50 | } 51 | 52 | void stackful_task_void(experimental::context> c) 53 | { 54 | static_assert(experimental::detail::has_await_transform); 55 | BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); 56 | } 57 | 58 | CO_TEST_CASE(task_void) 59 | { 60 | co_await experimental::make_context(&stackful_task_void); 61 | } 62 | 63 | int stackful_generator(experimental::context> c) 64 | { 65 | static_assert(experimental::detail::has_await_transform); 66 | BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); 67 | 68 | c.yield(1); 69 | c.yield(2); 70 | c.yield(3); 71 | c.yield(4); 72 | 73 | return 5; 74 | } 75 | 76 | CO_TEST_CASE(generator_) 77 | { 78 | generator g = experimental::make_context(&stackful_generator); 79 | BOOST_CHECK(1 == co_await g); 80 | BOOST_CHECK(2 == co_await g); 81 | BOOST_CHECK(3 == co_await g); 82 | BOOST_CHECK(4 == co_await g); 83 | BOOST_CHECK(5 == co_await g); 84 | } 85 | 86 | BOOST_AUTO_TEST_SUITE_END(); 87 | -------------------------------------------------------------------------------- /test/experimental/yield_context.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../test.hpp" 14 | 15 | using namespace boost::cobalt; 16 | 17 | BOOST_AUTO_TEST_SUITE(asio_yield_context); 18 | 19 | struct awaitable 20 | { 21 | bool await_ready() { return ready;} 22 | 23 | bool await_suspend(std::coroutine_handle h) { this->h = h; return suspend;} 24 | 25 | int await_resume() {return value;} 26 | 27 | std::coroutine_handle h; 28 | bool ready{false}; 29 | bool suspend{true}; 30 | int value; 31 | }; 32 | 33 | awaitable aw; 34 | 35 | int test_impl(boost::asio::yield_context ctx) 36 | { 37 | return experimental::await(aw, ctx); 38 | } 39 | 40 | BOOST_AUTO_TEST_CASE(ready) 41 | { 42 | boost::asio::io_context ctx; 43 | 44 | boost::asio::spawn(ctx, &test_impl, 45 | [](std::exception_ptr ep, int i) 46 | { 47 | BOOST_CHECK_EQUAL(i, 42); 48 | BOOST_CHECK(!ep); 49 | }); 50 | aw.ready = true; 51 | aw.suspend = false; 52 | aw.value = 42; 53 | 54 | ctx.run(); 55 | 56 | aw.h = {}; 57 | } 58 | 59 | BOOST_AUTO_TEST_CASE(no_suspend) 60 | { 61 | boost::asio::io_context ctx; 62 | 63 | boost::asio::spawn(ctx, &test_impl, 64 | [](std::exception_ptr ep, int i) 65 | { 66 | BOOST_CHECK_EQUAL(i, 43); 67 | BOOST_CHECK(!ep); 68 | }); 69 | aw.ready = false; 70 | aw.suspend = false; 71 | aw.value = 43; 72 | 73 | ctx.run(); 74 | 75 | aw.h = {}; 76 | } 77 | 78 | 79 | task t() 80 | { 81 | co_return; 82 | } 83 | 84 | struct dummy_aw 85 | { 86 | bool await_ready() { return false;} 87 | 88 | std::coroutine_handle await_suspend(std::coroutine_handle h) { return h;} 89 | void await_resume() {} 90 | }; 91 | 92 | BOOST_AUTO_TEST_CASE(await) 93 | { 94 | boost::asio::io_context ioc; 95 | 96 | boost::asio::spawn(ioc, 97 | [&](boost::asio::basic_yield_context ctx) 98 | { 99 | experimental::await(dummy_aw(), ctx); 100 | experimental::await(t(), ctx); 101 | }, 102 | [](std::exception_ptr ep) 103 | { 104 | BOOST_CHECK(!ep); 105 | }); 106 | 107 | ioc.run(); 108 | 109 | } 110 | 111 | 112 | BOOST_AUTO_TEST_CASE(destroy) 113 | { 114 | boost::asio::io_context ctx; 115 | 116 | boost::asio::spawn(ctx, &test_impl, 117 | [](std::exception_ptr ep, int i) 118 | { 119 | BOOST_CHECK(false); 120 | }); 121 | aw.ready = false; 122 | aw.suspend = true; 123 | aw.h = nullptr; 124 | ctx.run(); 125 | // ASAN will complain if the yield_context doesn't get freed. 126 | BOOST_CHECK(aw.h != nullptr); 127 | aw.h.destroy(); 128 | } 129 | 130 | 131 | BOOST_AUTO_TEST_SUITE_END(); 132 | -------------------------------------------------------------------------------- /test/fork.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | 9 | #include 10 | using namespace boost; 11 | 12 | #include 13 | #include "test.hpp" 14 | 15 | BOOST_AUTO_TEST_SUITE(fork_); 16 | 17 | struct tester : cobalt::detail::fork::shared_state 18 | { 19 | char buf[4096]; 20 | 21 | tester() : cobalt::detail::fork::shared_state{buf, 4096} {} 22 | 23 | static cobalt::detail::fork step(tester & , int i= 0) 24 | { 25 | if (i == 42) 26 | co_await cobalt::detail::fork::wired_up; 27 | co_return; 28 | } 29 | }; 30 | 31 | CO_TEST_CASE(fork) 32 | { 33 | tester t; 34 | auto x = tester::step(t); 35 | BOOST_CHECK(x.done()); 36 | 37 | x = tester::step(t, 42); 38 | 39 | BOOST_CHECK(!x.done()); 40 | co_return; 41 | } 42 | 43 | BOOST_AUTO_TEST_SUITE_END(); 44 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | 9 | 10 | boost::cobalt::main co_main(int argc, char *argv[]) 11 | { 12 | boost::asio::steady_timer tim{co_await boost::asio::this_coro::executor, std::chrono::milliseconds(50)}; 13 | co_await tim.async_wait(boost::cobalt::use_op); 14 | 15 | co_return 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/main_compile.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | 9 | int main(int , char *[]) 10 | { 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /test/monotonic_resource.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | 10 | #include 11 | 12 | BOOST_AUTO_TEST_SUITE(monotonic_resource); 13 | 14 | BOOST_AUTO_TEST_CASE(basic) 15 | { 16 | char buf[1024]; 17 | boost::cobalt::detail::monotonic_resource res{buf, sizeof(buf)}; 18 | 19 | { 20 | std::vector> vec{}; 21 | 22 | for (int i = 0u; i < 4000; i++) 23 | vec.push_back(i); 24 | } 25 | { 26 | std::vector> vec{&res}; 27 | 28 | for (int i = 0u; i < 4000; i++) 29 | vec.push_back(i); 30 | } 31 | } 32 | 33 | BOOST_AUTO_TEST_CASE(too_small) 34 | { 35 | char buf[1]; 36 | boost::cobalt::detail::monotonic_resource res{buf, sizeof(buf)}; 37 | 38 | { 39 | std::vector> vec{}; 40 | 41 | for (int i = 0u; i < 4000; i++) 42 | vec.push_back(i); 43 | } 44 | { 45 | std::vector> vec{&res}; 46 | 47 | for (int i = 0u; i < 4000; i++) 48 | vec.push_back(i); 49 | } 50 | } 51 | 52 | BOOST_AUTO_TEST_CASE(no_buf) 53 | { 54 | boost::cobalt::detail::monotonic_resource res; 55 | 56 | { 57 | std::vector> vec{}; 58 | 59 | for (int i = 0u; i < 4000; i++) 60 | vec.push_back(i); 61 | } 62 | { 63 | std::vector> vec{&res}; 64 | 65 | for (int i = 0u; i < 4000; i++) 66 | vec.push_back(i); 67 | } 68 | } 69 | 70 | BOOST_AUTO_TEST_SUITE_END(); -------------------------------------------------------------------------------- /test/run.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | #include 9 | 10 | using namespace boost; 11 | 12 | BOOST_AUTO_TEST_SUITE(run); 13 | 14 | BOOST_AUTO_TEST_CASE(run) 15 | { 16 | BOOST_CHECK(42 == cobalt::run([]() -> cobalt::task {co_return 42;}())); 17 | 18 | asio::io_context ctx; 19 | cobalt::this_thread::set_executor(ctx.get_executor()); 20 | BOOST_CHECK(42 == cobalt::run([]() -> cobalt::task {co_return 42;}())); 21 | 22 | BOOST_CHECK(ctx.get_executor() == cobalt::this_thread::get_executor()); 23 | } 24 | 25 | BOOST_AUTO_TEST_SUITE_END(); 26 | -------------------------------------------------------------------------------- /test/sbo_resource.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | 10 | #include 11 | 12 | BOOST_AUTO_TEST_SUITE(sbo_resource); 13 | 14 | BOOST_AUTO_TEST_CASE(basic) 15 | { 16 | char buf[1024]; 17 | boost::cobalt::detail::sbo_resource res{buf, sizeof(buf)}; 18 | 19 | { 20 | std::vector> vec{&res}; 21 | 22 | for (int i = 0u; i < 4000; i++) 23 | vec.push_back(i); 24 | } 25 | } 26 | 27 | BOOST_AUTO_TEST_CASE(too_small) 28 | { 29 | char buf[1]; 30 | boost::cobalt::detail::sbo_resource res{buf, sizeof(buf)}; 31 | 32 | { 33 | std::vector> vec{&res}; 34 | 35 | for (int i = 0u; i < 4000; i++) 36 | vec.push_back(i); 37 | } 38 | } 39 | 40 | BOOST_AUTO_TEST_CASE(no_buf) 41 | { 42 | boost::cobalt::detail::sbo_resource res; 43 | { 44 | std::vector> vec{&res}; 45 | 46 | for (int i = 0u; i < 4000; i++) 47 | vec.push_back(i); 48 | } 49 | } 50 | 51 | BOOST_AUTO_TEST_SUITE_END(); -------------------------------------------------------------------------------- /test/strand.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(BOOST_COBALT_USE_BOOST_CONTAINER_PMR) 12 | #include 13 | #endif 14 | 15 | #include 16 | #include "test.hpp" 17 | 18 | using namespace boost; 19 | 20 | 21 | BOOST_AUTO_TEST_SUITE(strand); 22 | 23 | cobalt::promise do_the_thing() 24 | { 25 | static std::atomic unique = 0; 26 | 27 | asio::steady_timer tim{co_await cobalt::this_coro::executor, std::chrono::milliseconds(50)}; 28 | co_await tim.async_wait(cobalt::use_op); 29 | unique++; 30 | BOOST_CHECK(unique == 1); 31 | unique--; 32 | } 33 | 34 | #if !defined(BOOST_COBALT_USE_IO_CONTEXT) 35 | 36 | BOOST_AUTO_TEST_CASE(strand) 37 | { 38 | std::vector ths; 39 | 40 | asio::io_context ctx; 41 | asio::any_io_executor exec{asio::make_strand(ctx.get_executor())}; 42 | #if !defined(BOOST_COBALT_NO_PMR) 43 | cobalt::pmr::synchronized_pool_resource sync; 44 | #endif 45 | 46 | for (int i = 0; i < 8; i++) 47 | ths.push_back( 48 | std::thread{ 49 | [&] 50 | { 51 | #if !defined(BOOST_COBALT_NO_PMR) 52 | cobalt::this_thread::set_default_resource(&sync); 53 | #endif 54 | cobalt::this_thread::set_executor(exec); 55 | +do_the_thing(); 56 | ctx.run(); 57 | }}); 58 | 59 | 60 | for (auto & th : ths) 61 | th.join(); 62 | } 63 | 64 | #else 65 | 66 | BOOST_AUTO_TEST_CASE(dummy) {} 67 | 68 | #endif 69 | 70 | BOOST_AUTO_TEST_SUITE_END(); 71 | 72 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #define BOOST_TEST_MODULE cobalt_test 7 | #include 8 | -------------------------------------------------------------------------------- /test/thread.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include "test.hpp" 13 | #include "boost/cobalt/spawn.hpp" 14 | 15 | boost::cobalt::thread thr() 16 | { 17 | boost::asio::steady_timer tim{co_await boost::asio::this_coro::executor, std::chrono::milliseconds(100)}; 18 | 19 | auto exec = co_await boost::asio::this_coro::executor; 20 | co_await tim.async_wait(boost::cobalt::use_op); 21 | } 22 | 23 | BOOST_AUTO_TEST_SUITE(thread); 24 | 25 | BOOST_AUTO_TEST_CASE(run) 26 | { 27 | auto t = thr(); 28 | 29 | t.join(); 30 | try {t.get_executor();} catch(std::exception & e) {BOOST_CHECK_EQUAL(e.what(), std::string_view("bad executor")); } 31 | } 32 | 33 | 34 | boost::cobalt::thread thr_stop() 35 | { 36 | boost::asio::steady_timer tim{co_await boost::asio::this_coro::executor, std::chrono::milliseconds(100)}; 37 | 38 | #if !defined(BOOST_COBALT_USE_IO_CONTEXT) 39 | auto exec = co_await boost::asio::this_coro::executor; 40 | auto execp = exec.target(); 41 | BOOST_ASSERT(execp != nullptr); 42 | auto exc = *execp; 43 | #else 44 | auto exc = co_await boost::asio::this_coro::executor; 45 | #endif 46 | 47 | boost::asio::query(exc, boost::asio::execution::context).stop(); 48 | co_await tim.async_wait(boost::cobalt::use_op); 49 | } 50 | 51 | BOOST_AUTO_TEST_CASE(stop) 52 | { 53 | auto t = thr_stop(); 54 | t.join(); 55 | } 56 | 57 | CO_TEST_CASE(await_thread) 58 | { 59 | co_await thr(); 60 | 61 | auto th = thr_stop(); 62 | boost::asio::steady_timer tim{co_await boost::asio::this_coro::executor, std::chrono::milliseconds(200)}; 63 | co_await tim.async_wait(boost::cobalt::use_op); 64 | co_await th; 65 | try { 66 | BOOST_CHECK_THROW(co_await th, boost::system::system_error); 67 | } catch(...) {} 68 | } 69 | 70 | boost::cobalt::task on_thread() 71 | { 72 | co_return std::this_thread::get_id(); 73 | } 74 | 75 | CO_TEST_CASE(spawn_onto_thread) 76 | { 77 | using namespace boost; 78 | auto t = thr(); 79 | 80 | auto id = co_await cobalt::spawn(t.get_executor(), on_thread(), cobalt::use_op); 81 | auto id2 = t.get_id(); 82 | auto id3 = std::this_thread::get_id(); 83 | 84 | BOOST_CHECK(id == id2); 85 | BOOST_CHECK(id3 != id); 86 | 87 | if (t.joinable()) 88 | t.join(); 89 | } 90 | 91 | 92 | BOOST_AUTO_TEST_SUITE_END(); -------------------------------------------------------------------------------- /test/util.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | static_assert(boost::cobalt::detail::variadic_first() == 1u); 9 | static_assert(boost::cobalt::detail::variadic_first() == std::numeric_limits::max()); 10 | static_assert(boost::cobalt::detail::variadic_first() == std::numeric_limits::max()); 11 | 12 | static_assert(boost::cobalt::detail::get_variadic<0>(4.2, 3) == 4.2); 13 | static_assert(boost::cobalt::detail::get_variadic<1>(4.2, 3) == 3); 14 | static_assert(boost::cobalt::detail::get_variadic<0>(4, 2.) == 4u); 15 | static_assert(boost::cobalt::detail::get_variadic<1>(4, 2.3) == 2.3); 16 | 17 | static_assert(boost::cobalt::detail::variadic_has); 18 | static_assert(boost::cobalt::detail::variadic_has); 19 | static_assert(!boost::cobalt::detail::variadic_has); 20 | 21 | static_assert(std::is_same_v, double>); 22 | static_assert(std::is_same_v, int>); 23 | static_assert(std::is_same_v, double>); 24 | -------------------------------------------------------------------------------- /test/wait_group.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include "test.hpp" 16 | 17 | using namespace boost; 18 | 19 | cobalt::promise gdelay(asio::any_io_executor exec, 20 | std::chrono::milliseconds ms = std::chrono::milliseconds(25)) 21 | { 22 | if (ms.count() == 0u) 23 | co_return; 24 | if (ms == std::chrono::milliseconds ::max()) 25 | throw std::runtime_error("wdummy_throw"); 26 | 27 | asio::steady_timer tim{exec, ms}; 28 | co_await tim.async_wait(cobalt::use_op); 29 | } 30 | 31 | 32 | BOOST_AUTO_TEST_SUITE(wait_group); 33 | 34 | CO_TEST_CASE(grp) 35 | { 36 | auto e = co_await cobalt::this_coro::executor; 37 | 38 | using namespace std; 39 | 40 | cobalt::wait_group wg; 41 | wg.push_back(gdelay(e)); 42 | wg.push_back(gdelay(e, 0ms)); 43 | wg.push_back(gdelay(e, 10ms)); 44 | wg.push_back(gdelay(e, 20ms)); 45 | 46 | co_await asio::post(e, cobalt::use_op); 47 | BOOST_CHECK(wg.size() == 4u); 48 | BOOST_CHECK(wg.reap() == 1u); 49 | BOOST_CHECK(wg.size() == 3u); 50 | 51 | co_await wg.wait_one(); 52 | BOOST_CHECK(wg.size() == 2u); 53 | 54 | wg.cancel(); 55 | co_await wg; 56 | 57 | BOOST_CHECK(wg.size() == 0u); 58 | } 59 | 60 | 61 | BOOST_AUTO_TEST_SUITE_END(); 62 | 63 | -------------------------------------------------------------------------------- /test/wrappers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Klemens D. Morgenstern 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | #include 14 | 15 | BOOST_AUTO_TEST_SUITE(wrappers); 16 | 17 | BOOST_AUTO_TEST_CASE(regular) 18 | { 19 | boost::asio::io_context ctx; 20 | boost::cobalt::this_thread::set_executor(ctx.get_executor()); 21 | bool ran = false; 22 | 23 | #if !defined(BOOST_COBALT_NO_PMR) 24 | char buf[512]; 25 | boost::cobalt::pmr::monotonic_buffer_resource res{buf, 512}; 26 | struct completion 27 | { 28 | bool & ran; 29 | using allocator_type = boost::cobalt::pmr::polymorphic_allocator; 30 | allocator_type get_allocator() const { return alloc; } 31 | boost::cobalt::pmr::polymorphic_allocator alloc; 32 | void operator()() 33 | { 34 | ran = true; 35 | } 36 | }; 37 | 38 | auto p = boost::cobalt::detail::post_coroutine(ctx.get_executor(), 39 | completion{ran, boost::cobalt::pmr::polymorphic_allocator(&res)} 40 | ); 41 | #else 42 | auto p = boost::cobalt::detail::post_coroutine(ctx.get_executor(), [&]{ran = true;}); 43 | #endif 44 | BOOST_CHECK(p); 45 | BOOST_CHECK(!ran); 46 | p.resume(); 47 | BOOST_CHECK(!ran); 48 | ctx.run(); 49 | BOOST_CHECK(ran); 50 | } 51 | 52 | BOOST_AUTO_TEST_CASE(expire) 53 | { 54 | 55 | boost::asio::io_context ct2; 56 | boost::cobalt::this_thread::set_executor(ct2.get_executor()); 57 | auto h = boost::cobalt::detail::post_coroutine(ct2.get_executor(), boost::asio::detached); 58 | boost::cobalt::detail::self_destroy(h); 59 | } 60 | 61 | 62 | BOOST_AUTO_TEST_SUITE_END(); --------------------------------------------------------------------------------