├── .clang-format ├── .github ├── profiles │ ├── debug_boost │ └── debug_standalone └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── conanfile.py ├── examples ├── CMakeLists.txt └── example_sum.cpp ├── include └── asiochan │ ├── asio.hpp │ ├── asiochan.hpp │ ├── async_promise.hpp │ ├── channel.hpp │ ├── channel_buff_size.hpp │ ├── channel_concepts.hpp │ ├── detail │ ├── channel_buffer.hpp │ ├── channel_method_ops.hpp │ ├── channel_op_result_base.hpp │ ├── channel_shared_state.hpp │ ├── channel_waiter_list.hpp │ ├── overloaded.hpp │ ├── select_impl.hpp │ ├── send_slot.hpp │ └── type_traits.hpp │ ├── nothing_op.hpp │ ├── read_op.hpp │ ├── select.hpp │ ├── select_concepts.hpp │ ├── select_result.hpp │ ├── sendable.hpp │ └── write_op.hpp └── tests ├── CMakeLists.txt └── asiochan ├── CMakeLists.txt ├── test_channel.cpp └── test_main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | AccessModifierOffset: '-2' 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: 'false' 6 | AlignConsecutiveDeclarations: 'false' 7 | AlignOperands: 'true' 8 | AlignTrailingComments: 'true' 9 | AllowShortBlocksOnASingleLine: 'true' 10 | AllowShortFunctionsOnASingleLine: All 11 | AllowShortIfStatementsOnASingleLine: 'true' 12 | AllowShortLoopsOnASingleLine: 'false' 13 | AlwaysBreakTemplateDeclarations: 'Yes' 14 | BreakBeforeBraces: Allman 15 | BreakConstructorInitializersBeforeComma: 'true' 16 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 17 | ConstructorInitializerIndentWidth: '2' 18 | ContinuationIndentWidth: '4' 19 | ColumnLimit: 0 20 | Cpp11BracedListStyle: 'true' 21 | DerivePointerAlignment: 'false' 22 | FixNamespaceComments: 'true' 23 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 24 | Language: Cpp 25 | MaxEmptyLinesToKeep: '1' 26 | NamespaceIndentation: All 27 | PointerAlignment: Left 28 | SortIncludes: 'true' 29 | IncludeCategories: 30 | - Regex: '^<.*>$' 31 | Priority: 1 32 | - Regex: '^".*"$' 33 | Priority: 2 34 | SpaceAfterCStyleCast: 'false' 35 | SpaceBeforeAssignmentOperators: 'true' 36 | SpaceBeforeParens: ControlStatements 37 | SpaceInEmptyParentheses: 'false' 38 | SpacesBeforeTrailingComments: '2' 39 | SpacesInAngles: 'false' 40 | SpaceBeforeCpp11BracedList: false 41 | UseTab: Never 42 | BinPackArguments: 'false' 43 | BinPackParameters: 'false' 44 | ExperimentalAutoDetectBinPacking: 'false' 45 | AllowAllParametersOfDeclarationOnNextLine: 'false' 46 | ... 47 | -------------------------------------------------------------------------------- /.github/profiles/debug_boost: -------------------------------------------------------------------------------- 1 | [settings] 2 | arch=x86_64 3 | arch_build=x86_64 4 | build_type=Debug 5 | compiler=gcc 6 | compiler.libcxx=libstdc++11 7 | compiler.version=10 8 | compiler.cppstd=20 9 | os=Linux 10 | os_build=Linux 11 | 12 | [options] 13 | asiochan:asio=boost 14 | boost:header_only=True 15 | 16 | [build_requires] 17 | 18 | [env] 19 | CC=gcc-10 20 | CXX=g++-10 21 | -------------------------------------------------------------------------------- /.github/profiles/debug_standalone: -------------------------------------------------------------------------------- 1 | [settings] 2 | arch=x86_64 3 | arch_build=x86_64 4 | build_type=Debug 5 | compiler=gcc 6 | compiler.libcxx=libstdc++11 7 | compiler.version=10 8 | compiler.cppstd=20 9 | os=Linux 10 | os_build=Linux 11 | 12 | [options] 13 | asiochan:asio=standalone 14 | 15 | [build_requires] 16 | 17 | [env] 18 | CC=gcc-10 19 | CXX=g++-10 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-20.04 8 | 9 | strategy: 10 | matrix: 11 | profile: [ debug_boost, debug_standalone ] 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup PATH 18 | shell: bash 19 | run: echo "${HOME}/.local/bin" >> "${GITHUB_PATH}" 20 | 21 | - name: Setup conan 22 | shell: bash 23 | run: pip install conan 24 | 25 | - name: Create package 26 | shell: bash 27 | run: > 28 | conan create "${{github.workspace}}" 29 | --update 30 | --build=missing 31 | --profile="${{github.workspace}}/.github/profiles/${{matrix.profile}}" 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE files 2 | .idea 3 | .vscode 4 | .vs 5 | 6 | # Common build folder names 7 | build 8 | build-* 9 | cmake-build-* 10 | out 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | set(ASIOCHAN_VERSION 0.4.3) 3 | project(AsioChan VERSION "${ASIOCHAN_VERSION}") 4 | 5 | include(CheckCXXCompilerFlag) 6 | 7 | option(ASIOCHAN_USE_STANDALONE_ASIO "Use standalone ASIO instead of Boost.ASIO" OFF) 8 | 9 | add_library(asiochan INTERFACE) 10 | add_library(asiochan::asiochan ALIAS asiochan) 11 | target_compile_features(asiochan INTERFACE cxx_std_20) 12 | target_include_directories(asiochan INTERFACE include) 13 | 14 | set(COROUTINES_FLAG -fcoroutines) 15 | check_cxx_compiler_flag("${COROUTINES_FLAG}" COMPILER_HAS_COROUTINES_FLAG) 16 | if (COMPILER_HAS_COROUTINES_FLAG) 17 | target_compile_options(asiochan INTERFACE "${COROUTINES_FLAG}") 18 | endif() 19 | 20 | if (ASIOCHAN_USE_STANDALONE_ASIO) 21 | target_compile_definitions(asiochan INTERFACE ASIOCHAN_USE_STANDALONE_ASIO) 22 | endif() 23 | 24 | # Building the tests and examples requires Conan packages 25 | set(CONAN_BUILD_INFO_PATH "${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake") 26 | if (EXISTS "${CONAN_BUILD_INFO_PATH}") 27 | include("${CONAN_BUILD_INFO_PATH}") 28 | conan_basic_setup(TARGETS) 29 | 30 | if (ASIOCHAN_USE_STANDALONE_ASIO) 31 | target_link_libraries(asiochan INTERFACE CONAN_PKG::asio) 32 | else() 33 | target_link_libraries(asiochan INTERFACE CONAN_PKG::boost) 34 | endif() 35 | 36 | find_package(Threads REQUIRED) 37 | enable_testing() 38 | 39 | add_subdirectory(examples) 40 | add_subdirectory(tests) 41 | endif() 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michal Jankovič 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asio-chan 2 | [![Build](https://github.com/MiSo1289/asiochan/workflows/Build/badge.svg)](https://github.com/MiSo1289/asiochan/actions?query=workflow%3ABuild) 3 | ```c++ 4 | #include 5 | 6 | using namespace asiochan; 7 | ``` 8 | 9 | This library provides golang-inspired channel types to be used with ASIO `awaitable` coroutines. 10 | Channels allow bidirectional message passing and synchronization between coroutines. 11 | Both standalone and boost versions of ASIO are supported. 12 | See the [installing](#installing) section on how to install and select the ASIO distribution used. 13 | 14 | ### Example 15 | 16 | ```c++ 17 | auto sum_subtask( 18 | read_channel> in, 19 | write_channel out) 20 | -> asio::awaitable 21 | { 22 | auto sum = 0; 23 | while (auto value = co_await in.read()) 24 | { 25 | sum += *value; 26 | } 27 | 28 | co_await out.write(sum); 29 | } 30 | 31 | auto sum_task(std::span array, int num_tasks) 32 | -> asio::awaitable 33 | { 34 | auto executor = co_await asio::this_coro::executor; 35 | 36 | // Spawn N child routines, sharing the same in/out channels 37 | auto in = channel>{}; 38 | auto out = channel{}; 39 | for (auto i : std::views::iota(0, num_tasks)) 40 | { 41 | asio::co_spawn(executor, sum_subtask(in, out), asio::detached); 42 | } 43 | 44 | // Send the array to the child routines 45 | for (auto val : array) 46 | { 47 | co_await in.write(val); 48 | } 49 | 50 | for (auto i : std::views::iota(0, num_tasks - 1)) 51 | { 52 | // Join a task 53 | co_await in.write(std::nullopt); 54 | // Retrieve its result 55 | auto subresult = co_await out.read(); 56 | // Send it to another task 57 | co_await in.write(subresult); 58 | } 59 | 60 | // Join the last task 61 | co_await in.write(std::nullopt); 62 | // Retrieve the complete result 63 | co_return co_await out.read(); 64 | } 65 | 66 | auto main() -> int 67 | { 68 | auto tp = asio::thread_pool{}; 69 | 70 | auto numbers = std::vector(100); 71 | std::iota(numbers.begin(), numbers.end(), 1); 72 | 73 | auto task = asio::co_spawn(tp, sum_task(numbers, 10), asio::use_future); 74 | std::cout << "The result is " << task.get(); 75 | 76 | return 0; 77 | } 78 | ``` 79 | 80 | ### Features 81 | 82 | - Thread safety - all channel types are thread-safe. 83 | - Value semantics - channels are intended to be passed by value. Internally, a channel holds a `shared_ptr` to a shared state type, similar to `future` and `promise`. 84 | - Bidirectional - channels are bidirectional by default, but can be restricted to write or read only (similar to channels in golang). 85 | - Synchronization - by default, a writer will wait until someone reads the value. Readers and writers are queued in FIFO order. Similar to golang channels, it is possible to specify a buffer size; writing is wait-free as long as there is space in the buffer. A dynamically sized buffer that is always wait-free for the writer is also available. 86 | - Channels of `void` - channels that do not send any values and are used only for synchronization are also possible. When buffered, the buffer is implemented as a simple counter (and does not allocate even when dynamically sized). 87 | - It is possible to simultaneously await multiple alternative read / write channel operations, similar to go's `select` statement, see [select](#select). This allows e.g. for easy implementation of cancellation / timeouts. 88 | 89 | ### Interface 90 | 91 | #### Sendable 92 | ```c++ 93 | #include 94 | 95 | template 96 | concept sendable; 97 | ``` 98 | 99 | The `sendable` concept defines the requirements for types that can be sent via channels. It is satisfied by all nothrow-movable types, and `void`. 100 | 101 | #### Basic channel 102 | ```c++ 103 | #include 104 | 105 | template 106 | class basic_channel; 107 | 108 | template 109 | class basic_read_channel; 110 | 111 | template 112 | class basic_write_channel; 113 | ``` 114 | 115 | Bidirectional channels can be converted to matching read and write channel types as long as the value type, buffer size, and executor match. Read and write channels are not interconvertible, to preserve type-safety. `buff_size` (`size_t`) specifies the size of the internal buffer. When 0, the writer will always wait for a read. A special value `unbounded_channel_buff` can be used, in which case the buffer is dynamic and writers never wait. 116 | 117 | #### Convenience typedefs 118 | ```c++ 119 | template 120 | using channel = basic_channel; 121 | 122 | template 123 | using read_channel = basic_read_channel; 124 | 125 | template 126 | using write_channel = basic_write_channel; 127 | 128 | template 129 | using unbounded_channel = channel; 130 | 131 | template 132 | using unbounded_read_channel = read_channel; 133 | 134 | template 135 | using unbounded_write_channel = write_channel; 136 | ``` 137 | 138 | #### Constructor 139 | ```c++ 140 | channel chan1{}; // Default constructor - creates a new shared state 141 | auto chan2 = chan1; // Copy constructor - now shares state with chan1 142 | auto chan3 = std::move(chan); // Move constructor - chan1 is now invalid. 143 | ``` 144 | 145 | #### Read 146 | ```c++ 147 | channel chan{}; 148 | std::optional maybe_result = chan.try_read(); 149 | int result = co_await chan.read(); 150 | 151 | channel chan_void{}; 152 | bool success = chan_void.try_read(); 153 | co_await chan_void.read(); 154 | ``` 155 | 156 | The `try_read` method does not perform any waiting. If no value is available, `nullopt` (or `false` for `channel`) is returned. 157 | 158 | The `read` method will wait until a value is ready. 159 | 160 | #### Write 161 | ```c++ 162 | bool success = chan.try_write(1); 163 | co_await chan.write(1); 164 | 165 | bool success = chan_void.try_write(); 166 | co_await chan_void.write(); 167 | ``` 168 | 169 | The `try_write` method do not perform any waiting. If no waiter was ready and the internal buffer was full, `false` is returned. 170 | 171 | The `write` method will wait until a reader is ready. 172 | 173 | Note that for unbounded buffered channels, writing always succeeds and is without wait. To reflect this fact, the `try_write` method is not available, and `write` can be called without `co_await`. 174 | 175 | #### Select 176 | ```c++ 177 | #include 178 | ``` 179 | 180 | The `select` function allows awaiting on multiple alternative channel operations. The first ready operation will cancel all others. Cancellation is fully deterministic. For example, when you await reads on two different channels, only one of these will have a value consumed. 181 | 182 | ```c++ 183 | channel chan_void_1{}; 184 | channel chan_void_2{}; 185 | channel chan_int_1{}; 186 | channel chan_int_2{}; 187 | 188 | auto result = co_await select( 189 | ops::read(chan_void_1), 190 | ops::write(chan_void_2), 191 | ops::read(chan_int_1), 192 | ops::write(std::rand(), chan_int_2)); 193 | 194 | bool received_void = result.received(); 195 | bool sent_void = result.sent(); 196 | // Non-owning pointer inside the result object if int was received, nullptr otherwise. 197 | int* maybe_received_int = result.get_if_received(); 198 | bool sent_int = result.sent(); 199 | 200 | if (result.received()) 201 | { 202 | // The get_received method will throw bad_select_result_access if you get the type wrong. 203 | int received_int = result.get_received(); 204 | } 205 | ``` 206 | 207 | If you don't want to wait until some operation becomes ready, you can use the wait-free function `select_ready`. It must be passed some default wait-free operation as the last argument. An example of a wait-free operation is `nothing`: 208 | 209 | ```c++ 210 | auto result = select_ready( 211 | ops::read(chan_int_1), 212 | ops::write(std::rand(), chan_int_2), 213 | ops::nothing); 214 | 215 | // If nothing is an alternative, has_value() method is available... 216 | bool any_succeeded = result.has_value(); 217 | // .. and the result is contextually convertible to bool. 218 | if (result) 219 | { 220 | // ... 221 | } 222 | ``` 223 | 224 | Writing to an unbounded channel is also a wait-free operation, and can be thus be used as the default operation for `select_ready`. 225 | 226 | The `read` and `write` operations can accept multiple channels. 227 | This allows you to `select` between multiple write channels with the same `send_type` without copying the sent value: 228 | ```c++ 229 | channel chan_1{}; 230 | channel chan_2{}; 231 | std::string long_string = "..."; 232 | 233 | auto string_send_result = co_await select( 234 | ops::write(std::move(long_string), chan_1, chan_2)); 235 | ``` 236 | 237 | The `select_result` type remembers the shared state of the channel for which an operation succeeded. This allows disambiguation between channels of the same `send_type`: 238 | 239 | ```c++ 240 | bool sent_to_chan_1 = string_send_result.sent_to(chan_1); 241 | bool sent_to_chan_2 = string_send_result.sent_to(chan_2); 242 | 243 | auto string_recv_result = co_await select( 244 | ops::read(chan_1, chan_2)); 245 | 246 | bool recv_from_chan_1 = string_recv_result.received_from(chan_1); 247 | bool recv_from_chan_2 = string_recv_result.received_from(chan_2); 248 | // Similar to get_if() 249 | std::string* result = string_recv_result.get_if_received_from(chan_1); 250 | ``` 251 | 252 | ##### Example: timeouts 253 | 254 | The select feature can be useful for implementing timeouts on channel operations. 255 | 256 | ```c++ 257 | using std::chrono::steady_clock; 258 | using duration = steady_clock::duration; 259 | using namespace std::literals; 260 | 261 | auto set_timeout( 262 | asio::execution::executor auto executor, 263 | duration dur) 264 | -> read_channel 265 | { 266 | auto timer = asio::steady_timer{executor}; 267 | timer.expires_after(dur); 268 | 269 | auto timeout = channel{}; 270 | 271 | asio::co_spawn( 272 | executor, 273 | [=]() -> asio::awaitable { 274 | co_await timer.async_wait(asio::use_awaitable); 275 | co_await timeout.write(); 276 | }, 277 | asio::detached); 278 | 279 | return timeout; 280 | } 281 | 282 | auto accept_client_requests( 283 | write_channel requests_channel) 284 | -> asio::awaitable 285 | { 286 | while (true) 287 | { 288 | auto request_from_client = co_await /* ... */; 289 | co_await requests_channel.write(std::move(request_from_client)); 290 | } 291 | } 292 | 293 | auto timeout_example() 294 | -> asio::awaitable 295 | { 296 | auto executor = co_await asio::this_coro::executor; 297 | auto requests = channel{}; 298 | 299 | asio::co_spawn( 300 | executor, 301 | accept_client_requests(requests_channel), 302 | asio::detached); 303 | 304 | auto timeout = set_timeout(executor, 10s); 305 | auto result = co_await select( 306 | ops::read(requests), 307 | ops::read(timeout)); 308 | 309 | if (auto* request = result.get_if_received()) 310 | { 311 | // Handle request... 312 | } 313 | else 314 | { 315 | // Handle timeout... 316 | } 317 | } 318 | ``` 319 | 320 | ### Installing 321 | 322 | #### Selecting ASIO distribution 323 | 324 | By default, Boost.ASIO is used. To use with standalone ASIO: 325 | - When consuming as a Conan package - set the option `asio=standalone` 326 | - When consuming as a CMake subproject - set the cache variable `ASIOCHAN_USE_STANDALONE_ASIO=ON` 327 | - When consuming as headers - define the `ASIOCHAN_USE_STANDALONE_ASIO` macro 328 | 329 | #### Conan package 330 | 331 | If you use Conan to manage dependencies, you can install the library by cloning the repository, and running `conan create `. 332 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from conans import CMake, ConanFile, tools 4 | 5 | 6 | def get_version(): 7 | try: 8 | content = tools.load("CMakeLists.txt") 9 | version = re.search("set\\(ASIOCHAN_VERSION (.*)\\)", content).group(1) 10 | return version.strip() 11 | except OSError: 12 | return None 13 | 14 | 15 | class AsioChan(ConanFile): 16 | name = "asiochan" 17 | version = get_version() 18 | revision_mode = "scm" 19 | description = "C++20 coroutine channels for ASIO" 20 | homepage = "https://github.com/MiSo1289/asiochan" 21 | url = "https://github.com/MiSo1289/asiochan" 22 | license = "MIT" 23 | generators = "cmake" 24 | settings = ("os", "compiler", "arch", "build_type") 25 | exports_sources = ( 26 | "examples/*", 27 | "include/*", 28 | "tests/*", 29 | "CMakeLists.txt", 30 | ) 31 | build_requires = ( 32 | # Unit-test framework 33 | "catch2/2.13.3", 34 | ) 35 | options = { 36 | "asio": ["boost", "standalone"] 37 | } 38 | default_options = { 39 | "asio": "boost", 40 | } 41 | 42 | def requirements(self): 43 | if self.options.asio == "boost": 44 | self.requires("boost/1.75.0") 45 | else: 46 | self.requires("asio/1.18.1") 47 | 48 | def build(self): 49 | cmake = CMake(self) 50 | cmake.definitions["ASIOCHAN_USE_STANDALONE_ASIO"] = self.options.asio == "standalone" 51 | 52 | cmake.configure() 53 | cmake.build() 54 | 55 | if tools.get_env("CONAN_RUN_TESTS", True): 56 | cmake.test() 57 | 58 | def package(self): 59 | self.copy("*.hpp", dst="include", src="include") 60 | 61 | def package_id(self): 62 | self.info.header_only() 63 | 64 | def package_info(self): 65 | if self.options.asio == "standalone": 66 | self.cpp_info.defines = ["ASIOCHAN_USE_STANDALONE_ASIO"] 67 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(asiochan_example_sum) 2 | target_link_libraries( 3 | asiochan_example_sum 4 | 5 | PRIVATE 6 | Threads::Threads 7 | asiochan::asiochan 8 | ) 9 | target_sources( 10 | asiochan_example_sum 11 | 12 | PRIVATE 13 | example_sum.cpp 14 | ) 15 | -------------------------------------------------------------------------------- /examples/example_sum.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #ifdef ASIOCHAN_USE_STANDALONE_ASIO 13 | 14 | #include 15 | #include 16 | 17 | #else 18 | 19 | #include 20 | #include 21 | 22 | namespace asio = boost::asio; 23 | 24 | #endif 25 | 26 | auto sum_subtask( 27 | asiochan::read_channel> in, 28 | asiochan::write_channel out) 29 | -> asio::awaitable 30 | { 31 | auto sum = 0; 32 | while (auto value = co_await in.read()) 33 | { 34 | sum += *value; 35 | } 36 | 37 | co_await out.write(sum); 38 | } 39 | 40 | auto sum_task(std::span array, int num_tasks) 41 | -> asio::awaitable 42 | { 43 | auto executor = co_await asio::this_coro::executor; 44 | 45 | // Spawn N child routines, sharing the same in/out channels 46 | auto in = asiochan::channel>{}; 47 | auto out = asiochan::channel{}; 48 | for (auto i : std::views::iota(0, num_tasks)) 49 | { 50 | asio::co_spawn(executor, sum_subtask(in, out), asio::detached); 51 | } 52 | 53 | // Send the array to the child routines 54 | for (auto val : array) 55 | { 56 | co_await in.write(val); 57 | } 58 | 59 | for (auto i : std::views::iota(0, num_tasks - 1)) 60 | { 61 | // Join a task 62 | co_await in.write(std::nullopt); 63 | // Retrieve its result 64 | auto subresult = co_await out.read(); 65 | // Send it to another task 66 | co_await in.write(subresult); 67 | } 68 | 69 | // Join the last task 70 | co_await in.write(std::nullopt); 71 | // Retrieve the complete result 72 | co_return co_await out.read(); 73 | } 74 | 75 | auto main() -> int 76 | { 77 | auto tp = asio::thread_pool{}; 78 | 79 | auto numbers = std::vector(10'000); 80 | std::iota(numbers.begin(), numbers.end(), 1); 81 | 82 | auto start = std::chrono::steady_clock::now(); 83 | 84 | auto task = asio::co_spawn(tp, sum_task(numbers, 100), asio::use_future); 85 | auto result = task.get(); 86 | 87 | auto dur = std::chrono::steady_clock::now() - start; 88 | 89 | std::cout << "The result is: " << result << "\n"; 90 | std::cout << "Test duration: " << std::chrono::duration{dur}.count() << "s\n"; 91 | 92 | return EXIT_SUCCESS; 93 | } 94 | -------------------------------------------------------------------------------- /include/asiochan/asio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef ASIOCHAN_USE_STANDALONE_ASIO 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #else 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #endif 37 | 38 | namespace asiochan 39 | { 40 | #ifdef ASIOCHAN_USE_STANDALONE_ASIO 41 | namespace asio = ::asio; 42 | namespace system = ::std; 43 | #else 44 | namespace asio = ::boost::asio; 45 | namespace system = ::boost::system; 46 | #endif 47 | } // namespace asiochan 48 | -------------------------------------------------------------------------------- /include/asiochan/asiochan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asiochan/asio.hpp" 4 | #include "asiochan/async_promise.hpp" 5 | #include "asiochan/channel.hpp" 6 | #include "asiochan/channel_buff_size.hpp" 7 | #include "asiochan/channel_concepts.hpp" 8 | #include "asiochan/nothing_op.hpp" 9 | #include "asiochan/read_op.hpp" 10 | #include "asiochan/select.hpp" 11 | #include "asiochan/sendable.hpp" 12 | #include "asiochan/write_op.hpp" 13 | -------------------------------------------------------------------------------- /include/asiochan/async_promise.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "asiochan/asio.hpp" 13 | #include "asiochan/sendable.hpp" 14 | 15 | namespace asiochan 16 | { 17 | enum class async_promise_errc 18 | { 19 | broken_promise = 1, 20 | }; 21 | 22 | [[nodiscard]] inline auto make_error_code(async_promise_errc const errc) noexcept -> system::error_code 23 | { 24 | class async_promise_category final : public system::error_category 25 | { 26 | public: 27 | [[nodiscard]] auto name() const noexcept -> char const* override 28 | { 29 | return "awaitable promise"; 30 | } 31 | 32 | [[nodiscard]] auto message(int const errc) const -> std::string override 33 | { 34 | switch (static_cast(errc)) 35 | { 36 | case async_promise_errc::broken_promise: 37 | return "broken promise"; 38 | default: 39 | return "unknown"; 40 | } 41 | } 42 | }; 43 | 44 | static constinit auto category = async_promise_category{}; 45 | return system::error_code{static_cast(errc), category}; 46 | } 47 | } // namespace asiochan 48 | 49 | template <> 50 | struct asiochan::system::is_error_code_enum 51 | : std::true_type 52 | { 53 | }; 54 | 55 | namespace asiochan 56 | { 57 | namespace detail 58 | { 59 | template 60 | struct async_promise_traits 61 | { 62 | using handler_sig = void(std::exception_ptr error, T value); 63 | using impl_type = std::optional< 64 | asio::detail::awaitable_handler>; 65 | }; 66 | 67 | template 68 | struct async_promise_traits 69 | { 70 | using handler_sig = void(std::exception_ptr error); 71 | using impl_type = std::optional>; 72 | }; 73 | } // namespace detail 74 | 75 | template 76 | class async_promise 77 | { 78 | public: 79 | async_promise() noexcept = default; 80 | 81 | async_promise(async_promise&& other) noexcept 82 | : impl_{std::move(other.impl_)} { } 83 | 84 | auto operator=(async_promise&& other) noexcept -> async_promise& 85 | { 86 | if (this != &other) 87 | { 88 | reset(); 89 | 90 | if (other.valid()) 91 | { 92 | impl_.emplace(*std::move(other.impl_)); 93 | } 94 | } 95 | 96 | return *this; 97 | } 98 | 99 | template U> 100 | void set_value(U&& value) 101 | { 102 | assert(valid()); 103 | auto executor = asio::get_associated_executor(*impl_); 104 | asio::post( 105 | std::move(executor), 106 | std::bind_front(consume_impl(), nullptr, T{std::forward(value)})); 107 | } 108 | 109 | void set_value() requires std::is_void_v 110 | { 111 | assert(valid()); 112 | auto executor = asio::get_associated_executor(*impl_); 113 | asio::post( 114 | std::move(executor), 115 | std::bind_front(consume_impl(), nullptr)); 116 | } 117 | 118 | void set_exception(std::exception_ptr error) requires std::default_initializable 119 | { 120 | assert(valid()); 121 | auto executor = asio::get_associated_executor(*impl_); 122 | 123 | asio::post( 124 | std::move(executor), 125 | std::bind_front(std::move(*impl_), std::move(error), T{})); 126 | 127 | impl_.reset(); 128 | } 129 | 130 | void set_exception(std::exception_ptr error) requires std::is_void_v 131 | { 132 | assert(valid()); 133 | auto executor = asio::get_associated_executor(*impl_); 134 | 135 | asio::post( 136 | std::move(executor), 137 | std::bind_front(std::move(*impl_), std::move(error))); 138 | 139 | impl_.reset(); 140 | } 141 | 142 | // clang-format off 143 | void set_error_code(system::error_code const error) 144 | requires std::is_void_v or std::default_initializable 145 | // clang-format on 146 | { 147 | set_exception(std::make_exception_ptr(system::system_error{error})); 148 | } 149 | 150 | void reset() 151 | { 152 | if (valid()) 153 | { 154 | set_error_code(async_promise_errc::broken_promise); 155 | } 156 | } 157 | 158 | [[nodiscard]] auto valid() const noexcept -> bool 159 | { 160 | return impl_.has_value(); 161 | } 162 | 163 | [[nodiscard]] auto get_awaitable() 164 | -> asio::awaitable 165 | { 166 | return get_awaitable([]() {}); 167 | } 168 | 169 | // clang-format off 170 | template 171 | requires std::invocable 172 | [[nodiscard]] auto get_awaitable(Continuation continuation, Args... args) 173 | -> asio::awaitable 174 | // clang-format on 175 | { 176 | assert(not valid()); 177 | return asio::async_initiate< 178 | asio::use_awaitable_t const, 179 | typename traits_type::handler_sig>( 180 | [](auto&& resumeCb, auto* self, auto continuation, auto... args) 181 | { 182 | self->impl_.emplace(std::move(resumeCb)); 183 | std::invoke(std::move(continuation), std::move(args)...); 184 | }, 185 | asio::use_awaitable_t{}, 186 | this, 187 | std::move(continuation), 188 | std::move(args)...); 189 | } 190 | 191 | private: 192 | using traits_type = detail::async_promise_traits; 193 | 194 | typename traits_type::impl_type impl_; 195 | 196 | [[nodiscard]] auto consume_impl() 197 | { 198 | assert(valid()); 199 | auto result = std::move(*impl_); 200 | impl_.reset(); 201 | return result; 202 | } 203 | }; 204 | 205 | // clang-format off 206 | template 210 | requires std::invocable&&, Args&&...> 211 | [[nodiscard]] auto suspend_with_promise(Continuation continuation, Args... args) 212 | -> asio::awaitable 213 | // clang-format on 214 | { 215 | auto promise = async_promise{}; 216 | co_return co_await promise.get_awaitable( 217 | [&promise](auto continuation, auto... args) 218 | { 219 | std::invoke(std::move(continuation), std::move(promise), std::move(args)...); 220 | }, 221 | std::move(continuation), 222 | std::move(args)...); 223 | } 224 | } // namespace asiochan 225 | -------------------------------------------------------------------------------- /include/asiochan/channel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "asiochan/asio.hpp" 7 | #include "asiochan/channel_buff_size.hpp" 8 | #include "asiochan/channel_concepts.hpp" 9 | #include "asiochan/detail/channel_method_ops.hpp" 10 | #include "asiochan/detail/channel_shared_state.hpp" 11 | #include "asiochan/sendable.hpp" 12 | 13 | namespace asiochan 14 | { 15 | template 19 | class channel_base 20 | { 21 | public: 22 | using executor_type = Executor; 23 | using shared_state_type = detail::channel_shared_state; 24 | using send_type = T; 25 | 26 | static constexpr auto flags = flags_; 27 | 28 | [[nodiscard]] channel_base() 29 | : shared_state_{std::make_shared()} 30 | { 31 | } 32 | 33 | // clang-format off 34 | template 35 | requires ((other_flags & flags) == flags) 36 | [[nodiscard]] channel_base( 37 | channel_base const& other) 38 | : shared_state_{other.shared_state_} 39 | // clang-format on 40 | { 41 | } 42 | 43 | // clang-format off 44 | template 45 | requires ((other_flags & flags) == flags) 46 | [[nodiscard]] channel_base( 47 | channel_base&& other) 48 | : shared_state_{std::move(other.shared_state_)} 49 | // clang-format on 50 | { 51 | } 52 | 53 | [[nodiscard]] auto shared_state() noexcept -> shared_state_type& 54 | { 55 | return *shared_state_; 56 | } 57 | 58 | [[nodiscard]] friend auto operator==( 59 | channel_base const& lhs, 60 | channel_base const& rhs) noexcept -> bool 61 | = default; 62 | 63 | protected: 64 | ~channel_base() noexcept = default; 65 | 66 | private: 67 | template 68 | friend class channel_base; 69 | 70 | std::shared_ptr shared_state_; 71 | }; 72 | 73 | template 74 | class basic_channel 75 | : public channel_base, 76 | public detail::channel_method_ops> 77 | { 78 | private: 79 | using base = channel_base; 80 | using ops = detail::channel_method_ops>; 81 | 82 | public: 83 | using base::base; 84 | 85 | using ops::try_read; 86 | 87 | using ops::read; 88 | 89 | using ops::try_write; 90 | 91 | using ops::write; 92 | }; 93 | 94 | template 95 | class basic_read_channel 96 | : public channel_base, 97 | public detail::channel_method_ops> 98 | { 99 | private: 100 | using base = channel_base; 101 | using ops = detail::channel_method_ops>; 102 | 103 | public: 104 | using base::base; 105 | 106 | using ops::try_read; 107 | 108 | using ops::read; 109 | }; 110 | 111 | template 112 | class basic_write_channel 113 | : public channel_base, 114 | public detail::channel_method_ops> 115 | { 116 | private: 117 | using base = channel_base; 118 | using ops = detail::channel_method_ops>; 119 | 120 | public: 121 | using base::base; 122 | 123 | using ops::try_write; 124 | 125 | using ops::write; 126 | }; 127 | 128 | template 129 | using channel = basic_channel; 130 | 131 | template 132 | using read_channel = basic_read_channel; 133 | 134 | template 135 | using write_channel = basic_write_channel; 136 | 137 | template 138 | using unbounded_channel = channel; 139 | 140 | template 141 | using unbounded_read_channel = read_channel; 142 | 143 | template 144 | using unbounded_write_channel = write_channel; 145 | } // namespace asiochan 146 | -------------------------------------------------------------------------------- /include/asiochan/channel_buff_size.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace asiochan 7 | { 8 | using channel_buff_size = std::size_t; 9 | 10 | inline constexpr auto unbounded_channel_buff = std::numeric_limits::max(); 11 | } // namespace asiochan 12 | -------------------------------------------------------------------------------- /include/asiochan/channel_concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "asiochan/detail/channel_shared_state.hpp" 6 | 7 | namespace asiochan 8 | { 9 | enum channel_flags : unsigned 10 | { 11 | readable = 1u << 0u, 12 | writable = 1u << 1u, 13 | bidirectional = readable | writable, 14 | }; 15 | 16 | // clang-format off 17 | template 18 | concept any_channel_type = requires (T& channel, T const& const_channel) 19 | { 20 | typename T::executor_type; 21 | requires asio::execution::executor; 22 | 23 | typename T::shared_state_type; 24 | typename T::send_type; 25 | 26 | requires detail::channel_shared_state_type< 27 | typename T::shared_state_type, 28 | typename T::send_type, 29 | typename T::executor_type>; 30 | 31 | { channel.shared_state() } noexcept -> std::same_as; 32 | }; 33 | 34 | template 35 | concept any_readable_channel_type 36 | = any_channel_type and (static_cast(T::flags & channel_flags::readable)); 37 | 38 | template 39 | concept any_writable_channel_type 40 | = any_channel_type and (static_cast(T::flags & channel_flags::writable)); 41 | 42 | template 43 | concept any_bidirectional_channel_type 44 | = any_readable_channel_type and any_writable_channel_type; 45 | 46 | template 47 | concept channel_type 48 | = any_channel_type and std::same_as; 49 | 50 | template 51 | concept readable_channel_type 52 | = channel_type and any_readable_channel_type; 53 | 54 | template 55 | concept writable_channel_type 56 | = channel_type and any_writable_channel_type; 57 | 58 | template 59 | concept bidirectional_channel_type 60 | = channel_type and any_bidirectional_channel_type; 61 | // clang-format on 62 | } // namespace asiochan 63 | -------------------------------------------------------------------------------- /include/asiochan/detail/channel_buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "asiochan/channel_buff_size.hpp" 11 | #include "asiochan/detail/send_slot.hpp" 12 | #include "asiochan/sendable.hpp" 13 | 14 | namespace asiochan::detail 15 | { 16 | template 17 | class channel_buffer 18 | { 19 | public: 20 | [[nodiscard]] auto empty() const noexcept -> bool 21 | { 22 | return count_ == 0; 23 | } 24 | 25 | [[nodiscard]] auto full() const noexcept -> bool 26 | { 27 | return count_ == size; 28 | } 29 | 30 | void enqueue(send_slot& from) noexcept 31 | { 32 | assert(not full()); 33 | transfer(from, buff_[(head_ + count_++) % size]); 34 | } 35 | 36 | void dequeue(send_slot& to) noexcept 37 | { 38 | assert(not empty()); 39 | --count_; 40 | transfer(buff_[std::exchange(head_, (head_ + 1) % size)], to); 41 | } 42 | 43 | private: 44 | std::size_t head_ = 0; 45 | std::size_t count_ = 0; 46 | std::array, size> buff_; 47 | }; 48 | 49 | // clang-format off 50 | template 51 | requires (size > 0) 52 | class channel_buffer 53 | // clang-format on 54 | { 55 | public: 56 | [[nodiscard]] auto empty() const noexcept -> bool 57 | { 58 | return count_ == 0; 59 | } 60 | 61 | [[nodiscard]] auto full() const noexcept -> bool 62 | { 63 | if constexpr (size != unbounded_channel_buff) 64 | { 65 | return count_ == size; 66 | } 67 | else 68 | { 69 | return false; 70 | } 71 | } 72 | 73 | void enqueue(send_slot& from) noexcept 74 | { 75 | assert(not full()); 76 | ++count_; 77 | } 78 | 79 | void dequeue(send_slot& to) noexcept 80 | { 81 | assert(not empty()); 82 | --count_; 83 | } 84 | 85 | private: 86 | std::size_t count_ = 0; 87 | }; 88 | 89 | template 90 | class channel_buffer 91 | { 92 | public: 93 | [[nodiscard]] auto empty() const noexcept -> bool 94 | { 95 | return true; 96 | } 97 | 98 | [[nodiscard]] auto full() const noexcept -> bool 99 | { 100 | return true; 101 | } 102 | 103 | [[noreturn]] void enqueue(send_slot& from) noexcept 104 | { 105 | std::terminate(); 106 | } 107 | 108 | [[noreturn]] void dequeue(send_slot& to) noexcept 109 | { 110 | std::terminate(); 111 | } 112 | }; 113 | 114 | // clang-format off 115 | template 116 | requires (not std::is_void_v) 117 | class channel_buffer 118 | // clang-format on 119 | { 120 | public: 121 | [[nodiscard]] auto empty() const noexcept -> bool 122 | { 123 | return queue_.empty(); 124 | } 125 | 126 | [[nodiscard]] static auto full() noexcept -> bool 127 | { 128 | return false; 129 | } 130 | 131 | void enqueue(send_slot& from) 132 | { 133 | queue_.push(from.read()); 134 | } 135 | 136 | void dequeue(send_slot& to) noexcept 137 | { 138 | assert(not empty()); 139 | to.write(std::move(queue_.front())); 140 | queue_.pop(); 141 | } 142 | 143 | private: 144 | std::queue queue_; 145 | }; 146 | } // namespace asiochan::detail 147 | -------------------------------------------------------------------------------- /include/asiochan/detail/channel_method_ops.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "asiochan/asio.hpp" 7 | #include "asiochan/channel_buff_size.hpp" 8 | #include "asiochan/channel_concepts.hpp" 9 | #include "asiochan/nothing_op.hpp" 10 | #include "asiochan/read_op.hpp" 11 | #include "asiochan/select.hpp" 12 | #include "asiochan/sendable.hpp" 13 | #include "asiochan/write_op.hpp" 14 | 15 | namespace asiochan::detail 16 | { 17 | template 22 | class channel_method_ops 23 | { 24 | public: 25 | // clang-format off 26 | [[nodiscard]] auto try_read() -> std::optional 27 | requires (static_cast(flags & readable)) 28 | // clang-format on 29 | { 30 | auto result = select_ready( 31 | ops::read(derived()), 32 | ops::nothing); 33 | 34 | if (auto const ptr = result.template get_if_received()) 35 | { 36 | return std::move(*ptr); 37 | } 38 | 39 | return std::nullopt; 40 | } 41 | 42 | // clang-format off 43 | [[nodiscard]] auto try_write(T value) -> bool 44 | requires (static_cast(flags & writable)) 45 | and (buff_size != unbounded_channel_buff) 46 | // clang-format on 47 | { 48 | auto const result = select_ready( 49 | ops::write(std::move(value), derived()), 50 | ops::nothing); 51 | 52 | return result.has_value(); 53 | } 54 | 55 | // clang-format off 56 | [[nodiscard]] auto read() -> asio::awaitable 57 | requires (static_cast(flags & readable)) 58 | // clang-format on 59 | { 60 | auto result = co_await select(ops::read(derived())); 61 | 62 | co_return std::move(result).template get_received(); 63 | } 64 | 65 | // clang-format off 66 | [[nodiscard]] auto write(T value) -> asio::awaitable 67 | requires (static_cast(flags & writable)) 68 | and (buff_size != unbounded_channel_buff) 69 | // clang-format on 70 | { 71 | co_await select(ops::write(std::move(value), derived())); 72 | } 73 | 74 | // clang-format off 75 | void write(T value) 76 | requires (static_cast(flags & writable)) 77 | and (buff_size == unbounded_channel_buff) 78 | // clang-format on 79 | { 80 | select_ready(ops::write(std::move(value), derived())); 81 | } 82 | 83 | private: 84 | [[nodiscard]] auto derived() noexcept -> Derived& 85 | { 86 | return static_cast(*this); 87 | } 88 | }; 89 | 90 | template 94 | class channel_method_ops 95 | { 96 | public: 97 | // clang-format off 98 | [[nodiscard]] auto try_read() -> bool 99 | requires (static_cast(flags & readable)) 100 | // clang-format on 101 | { 102 | auto const result = select_ready( 103 | ops::read(derived()), 104 | ops::nothing); 105 | 106 | return result.has_value(); 107 | } 108 | 109 | // clang-format off 110 | [[nodiscard]] auto try_write() -> bool 111 | requires (static_cast(flags & writable)) 112 | and (buff_size != unbounded_channel_buff) 113 | // clang-format on 114 | { 115 | auto const result = select_ready( 116 | ops::write(derived()), 117 | ops::nothing); 118 | 119 | return result.has_value(); 120 | } 121 | 122 | // clang-format off 123 | [[nodiscard]] auto read() -> asio::awaitable 124 | requires (static_cast(flags & readable)) 125 | // clang-format on 126 | { 127 | co_await select(ops::read(derived())); 128 | } 129 | 130 | // clang-format off 131 | [[nodiscard]] auto write() -> asio::awaitable 132 | requires (static_cast(flags & writable)) 133 | and (buff_size != unbounded_channel_buff) 134 | // clang-format on 135 | { 136 | co_await select(ops::write(derived())); 137 | } 138 | 139 | // clang-format off 140 | void write() 141 | requires (static_cast(flags & writable)) 142 | and (buff_size == unbounded_channel_buff) 143 | // clang-format on 144 | { 145 | select_ready(ops::write(derived())); 146 | } 147 | 148 | private: 149 | [[nodiscard]] auto derived() noexcept -> Derived& 150 | { 151 | return static_cast(*this); 152 | } 153 | }; 154 | } // namespace asiochan::detail 155 | -------------------------------------------------------------------------------- /include/asiochan/detail/channel_op_result_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asiochan/channel_concepts.hpp" 4 | #include "asiochan/sendable.hpp" 5 | 6 | namespace asiochan::detail 7 | { 8 | template 9 | class channel_op_result_base 10 | { 11 | public: 12 | explicit channel_op_result_base(channel_type auto& channel) 13 | : shared_state_{&channel.shared_state()} { } 14 | 15 | [[nodiscard]] static auto matches(any_channel_type auto const&) noexcept -> bool 16 | { 17 | return false; 18 | } 19 | 20 | [[nodiscard]] auto matches(channel_type auto const& channel) const noexcept -> bool 21 | { 22 | return &channel.shared_state() == shared_state_; 23 | } 24 | 25 | private: 26 | void* shared_state_ = nullptr; 27 | }; 28 | } // namespace asiochan::detail 29 | -------------------------------------------------------------------------------- /include/asiochan/detail/channel_shared_state.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "asiochan/asio.hpp" 7 | #include "asiochan/channel_buff_size.hpp" 8 | #include "asiochan/detail/channel_buffer.hpp" 9 | #include "asiochan/detail/channel_waiter_list.hpp" 10 | #include "asiochan/detail/send_slot.hpp" 11 | #include "asiochan/sendable.hpp" 12 | 13 | namespace asiochan::detail 14 | { 15 | template 16 | class channel_shared_state_writer_list_base 17 | { 18 | public: 19 | using writer_list_type = channel_waiter_list; 20 | 21 | static constexpr bool write_never_waits = false; 22 | 23 | [[nodiscard]] auto writer_list() noexcept -> writer_list_type& 24 | { 25 | return writer_list_; 26 | } 27 | 28 | private: 29 | writer_list_type writer_list_; 30 | }; 31 | 32 | template 33 | class channel_shared_state_writer_list_base 34 | { 35 | public: 36 | using writer_list_type = void; 37 | 38 | static constexpr bool write_never_waits = true; 39 | }; 40 | 41 | template 42 | class channel_shared_state 43 | : public channel_shared_state_writer_list_base 44 | { 45 | public: 46 | using mutex_type = std::mutex; 47 | using buffer_type = channel_buffer; 48 | using reader_list_type = channel_waiter_list; 49 | 50 | static constexpr auto buff_size = buff_size_; 51 | 52 | [[nodiscard]] auto reader_list() noexcept -> reader_list_type& 53 | { 54 | return reader_list_; 55 | } 56 | 57 | [[nodiscard]] auto buffer() noexcept -> buffer_type& 58 | { 59 | return buffer_; 60 | } 61 | 62 | [[nodiscard]] auto mutex() noexcept -> mutex_type& 63 | { 64 | return mutex_; 65 | } 66 | 67 | private: 68 | mutex_type mutex_; 69 | reader_list_type reader_list_; 70 | [[no_unique_address]] buffer_type buffer_; 71 | }; 72 | 73 | template 74 | struct is_channel_shared_state 75 | : std::false_type 76 | { 77 | }; 78 | 79 | template 82 | struct is_channel_shared_state< 83 | channel_shared_state, 84 | SendType, 85 | Executor> 86 | : std::true_type 87 | { 88 | }; 89 | 90 | template 91 | inline constexpr auto is_channel_shared_state_type_v 92 | = is_channel_shared_state::value; 93 | 94 | template 95 | concept channel_shared_state_type 96 | = is_channel_shared_state_type_v; 97 | } // namespace asiochan::detail 98 | -------------------------------------------------------------------------------- /include/asiochan/detail/channel_waiter_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "asiochan/async_promise.hpp" 8 | #include "asiochan/detail/send_slot.hpp" 9 | #include "asiochan/sendable.hpp" 10 | 11 | namespace asiochan::detail 12 | { 13 | using select_waiter_token = std::size_t; 14 | 15 | template 16 | struct select_wait_context 17 | { 18 | async_promise promise; 19 | std::mutex mutex; 20 | bool avail_flag = true; 21 | }; 22 | 23 | template 24 | auto claim(select_wait_context& ctx) -> bool 25 | { 26 | auto const lock = std::scoped_lock{ctx.mutex}; 27 | return std::exchange(ctx.avail_flag, false); 28 | } 29 | 30 | template 31 | struct channel_waiter_list_node 32 | { 33 | select_wait_context* ctx = nullptr; 34 | send_slot* slot = nullptr; 35 | select_waiter_token token = 0; 36 | channel_waiter_list_node* prev = nullptr; 37 | channel_waiter_list_node* next = nullptr; 38 | }; 39 | 40 | template 41 | void notify_waiter(channel_waiter_list_node& waiter) 42 | { 43 | waiter.ctx->promise.set_value(waiter.token); 44 | } 45 | 46 | template 47 | class channel_waiter_list 48 | { 49 | public: 50 | using node_type = channel_waiter_list_node; 51 | 52 | void enqueue(node_type& node) noexcept 53 | { 54 | node.prev = last_; 55 | node.next = nullptr; 56 | 57 | if (not first_) 58 | { 59 | first_ = &node; 60 | } 61 | else 62 | { 63 | last_->next = &node; 64 | } 65 | 66 | last_ = &node; 67 | } 68 | 69 | void dequeue(node_type& node) noexcept 70 | { 71 | if (&node == first_) 72 | { 73 | first_ = node.next; 74 | } 75 | if (&node == last_) 76 | { 77 | last_ = node.prev; 78 | } 79 | if (node.prev) 80 | { 81 | node.prev->next = node.next; 82 | node.prev = nullptr; 83 | } 84 | if (node.next) 85 | { 86 | node.next->prev = node.prev; 87 | node.next = nullptr; 88 | } 89 | } 90 | 91 | auto dequeue_first_available( 92 | std::same_as> auto&... contexts) noexcept 93 | -> node_type* 94 | { 95 | while (first_) 96 | { 97 | auto const node = first_; 98 | 99 | auto const pop = [&]() 100 | { 101 | first_ = node->next; 102 | if (not first_) 103 | { 104 | last_ = nullptr; 105 | } 106 | else 107 | { 108 | first_->prev = nullptr; 109 | node->next = nullptr; 110 | } 111 | }; 112 | 113 | auto const lock = std::scoped_lock{node->ctx->mutex, contexts.mutex...}; 114 | if (node->ctx->avail_flag) 115 | { 116 | if (not(contexts.avail_flag and ...)) 117 | { 118 | return nullptr; 119 | } 120 | 121 | node->ctx->avail_flag = false; 122 | ((contexts.avail_flag = false), ...); 123 | 124 | pop(); 125 | 126 | return node; 127 | } 128 | 129 | pop(); 130 | } 131 | 132 | return nullptr; 133 | } 134 | 135 | private: 136 | node_type* first_ = nullptr; 137 | node_type* last_ = nullptr; 138 | }; 139 | } // namespace asiochan::detail 140 | -------------------------------------------------------------------------------- /include/asiochan/detail/overloaded.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace asiochan::detail 4 | { 5 | template 6 | struct overloaded : Ts... 7 | { 8 | using Ts::operator()...; 9 | }; 10 | 11 | template 12 | overloaded(Ts...) -> overloaded; 13 | } // namespace asiochan::detail 14 | -------------------------------------------------------------------------------- /include/asiochan/detail/select_impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "asiochan/asio.hpp" 11 | #include "asiochan/async_promise.hpp" 12 | #include "asiochan/detail/channel_waiter_list.hpp" 13 | #include "asiochan/select_concepts.hpp" 14 | #include "asiochan/select_result.hpp" 15 | 16 | namespace asiochan::detail 17 | { 18 | template 19 | inline constexpr auto select_ops_base_tokens = []() 20 | { 21 | auto token_base = std::size_t{0}; 22 | return std::array{ 23 | std::exchange(token_base, token_base + Ops::num_alternatives)..., 24 | }; 25 | }(); 26 | } // namespace asiochan::detail 27 | -------------------------------------------------------------------------------- /include/asiochan/detail/send_slot.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "asiochan/sendable.hpp" 8 | 9 | namespace asiochan::detail 10 | { 11 | template 12 | class send_slot 13 | { 14 | public: 15 | auto read() noexcept -> T 16 | { 17 | assert(value_.has_value()); 18 | return *std::exchange(value_, {}); 19 | } 20 | 21 | void write(T&& value) noexcept 22 | { 23 | assert(not value_.has_value()); 24 | value_.emplace(std::move(value)); 25 | } 26 | 27 | friend void transfer(send_slot& from, send_slot& to) noexcept 28 | { 29 | assert(from.value_.has_value()); 30 | assert(not to.value_.has_value()); 31 | to.value_.emplace(*std::move(from.value_)); 32 | } 33 | 34 | private: 35 | std::optional value_ = std::nullopt; 36 | }; 37 | 38 | template <> 39 | class send_slot 40 | { 41 | public: 42 | static void read() noexcept { } 43 | 44 | static void write() noexcept { } 45 | 46 | friend void transfer(send_slot&, send_slot&) noexcept 47 | { 48 | } 49 | }; 50 | } // namespace asiochan::detail 51 | -------------------------------------------------------------------------------- /include/asiochan/detail/type_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace asiochan::detail 6 | { 7 | template 8 | struct head : std::type_identity 9 | { 10 | }; 11 | 12 | template 13 | using head_t = typename head::type; 14 | 15 | template 16 | struct last : last 17 | { 18 | }; 19 | 20 | template 21 | struct last : std::type_identity 22 | { 23 | }; 24 | 25 | template 26 | using last_t = typename last::type; 27 | 28 | template 29 | struct constant 30 | { 31 | static constexpr auto value = value_; 32 | }; 33 | } // namespace asiochan::detail 34 | -------------------------------------------------------------------------------- /include/asiochan/nothing_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "asiochan/asio.hpp" 8 | #include "asiochan/channel_concepts.hpp" 9 | 10 | namespace asiochan 11 | { 12 | class no_result_t 13 | { 14 | public: 15 | [[nodiscard]] friend auto operator<=>( 16 | no_result_t const& lhs, 17 | no_result_t const& rhs) noexcept = default; 18 | 19 | [[nodiscard]] static auto matches(any_channel_type auto const&) noexcept -> bool 20 | { 21 | return false; 22 | } 23 | }; 24 | 25 | inline constexpr auto no_result = no_result_t{}; 26 | 27 | namespace ops 28 | { 29 | class nothing_t 30 | { 31 | public: 32 | using executor_type = asio::system_executor; 33 | using result_type = no_result_t; 34 | 35 | static constexpr auto num_alternatives = std::size_t{1}; 36 | static constexpr auto always_waitfree = true; 37 | 38 | [[nodiscard]] static auto get_executor() -> executor_type 39 | { 40 | return asio::system_executor{}; 41 | } 42 | 43 | [[nodiscard]] static auto submit_if_ready() noexcept -> std::optional 44 | { 45 | return 0; 46 | } 47 | 48 | [[nodiscard]] static auto get_result( 49 | [[maybe_unused]] std::optional successful_alternative) noexcept 50 | -> no_result_t 51 | { 52 | return no_result; 53 | } 54 | }; 55 | 56 | inline constexpr auto nothing = nothing_t{}; 57 | } // namespace ops 58 | } // namespace asiochan 59 | -------------------------------------------------------------------------------- /include/asiochan/read_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "asiochan/asio.hpp" 11 | #include "asiochan/channel_concepts.hpp" 12 | #include "asiochan/detail/channel_op_result_base.hpp" 13 | #include "asiochan/detail/channel_waiter_list.hpp" 14 | #include "asiochan/detail/send_slot.hpp" 15 | #include "asiochan/select_concepts.hpp" 16 | #include "asiochan/sendable.hpp" 17 | 18 | namespace asiochan 19 | { 20 | template 21 | class read_result : public detail::channel_op_result_base 22 | { 23 | private: 24 | using base = detail::channel_op_result_base; 25 | 26 | public: 27 | template U> 28 | read_result(U&& value, channel_type auto& channel) 29 | : base{channel} 30 | , value_{std::forward(value)} 31 | { 32 | } 33 | 34 | [[nodiscard]] auto get() & noexcept -> T& 35 | { 36 | return value_; 37 | } 38 | 39 | [[nodiscard]] auto get() const& noexcept -> T const& 40 | { 41 | return value_; 42 | } 43 | 44 | [[nodiscard]] auto get() && noexcept -> T&& 45 | { 46 | return std::move(value_); 47 | } 48 | 49 | [[nodiscard]] auto get() const&& noexcept -> T const&& 50 | { 51 | return std::move(value_); 52 | } 53 | 54 | private: 55 | T value_; 56 | }; 57 | 58 | template <> 59 | class read_result : public detail::channel_op_result_base 60 | { 61 | private: 62 | using base = read_result::channel_op_result_base; 63 | 64 | public: 65 | using base::base; 66 | 67 | static void get() noexcept { } 68 | }; 69 | 70 | namespace ops 71 | { 72 | template ChannelsHead, readable_channel_type... ChannelsTail> 73 | class read 74 | { 75 | public: 76 | using executor_type = typename ChannelsHead::executor_type; 77 | using result_type = read_result; 78 | using slot_type = detail::send_slot; 79 | using waiter_node_type = detail::channel_waiter_list_node; 80 | 81 | static constexpr auto num_alternatives = 1u + sizeof...(ChannelsTail); 82 | static constexpr auto always_waitfree = false; 83 | 84 | struct wait_state_type 85 | { 86 | std::array, num_alternatives> waiter_nodes = {}; 87 | }; 88 | 89 | explicit read(ChannelsHead& channels_head, ChannelsTail&... channels_tail) noexcept 90 | : channels_{channels_head, channels_tail...} 91 | { 92 | } 93 | 94 | [[nodiscard]] auto submit_if_ready() -> std::optional 95 | { 96 | auto ready_alternative = std::optional{}; 97 | 98 | ([&](std::index_sequence) 99 | { 100 | ([&](ChannelState& channel_state) 101 | { 102 | constexpr auto channel_index = indices; 103 | auto const lock = std::scoped_lock{channel_state.mutex()}; 104 | 105 | if constexpr (ChannelState::buff_size != 0) 106 | { 107 | if (not channel_state.buffer().empty()) 108 | { 109 | // Get a value from the buffer. 110 | channel_state.buffer().dequeue(slot_); 111 | ready_alternative = channel_index; 112 | 113 | if constexpr (not ChannelState::write_never_waits) 114 | { 115 | if (auto const writer = channel_state.writer_list().dequeue_first_available()) 116 | { 117 | // Buffer was full with writers waiting. 118 | // Wake the oldest writer and store his value in the buffer. 119 | channel_state.buffer().enqueue(*writer->slot); 120 | detail::notify_waiter(*writer); 121 | } 122 | } 123 | 124 | return true; 125 | } 126 | } 127 | else if (auto const writer = channel_state.writer_list().dequeue_first_available()) 128 | { 129 | // Get a value directly from a waiting writer. 130 | transfer(*writer->slot, slot_); 131 | detail::notify_waiter(*writer); 132 | ready_alternative = channel_index; 133 | 134 | return true; 135 | } 136 | 137 | return false; 138 | }(std::get(channels_).shared_state()) 139 | or ...); 140 | }(std::index_sequence_for{})); 141 | 142 | return ready_alternative; 143 | } 144 | 145 | [[nodiscard]] auto submit_with_wait( 146 | detail::select_wait_context& select_ctx, 147 | detail::select_waiter_token const base_token, 148 | wait_state_type& wait_state) 149 | -> std::optional 150 | { 151 | return ([&](std::index_sequence) 152 | { 153 | auto ready_alternative = std::optional{}; 154 | 155 | ([&](ChannelState& channel_state) 156 | { 157 | constexpr auto channel_index = indices; 158 | auto const token = base_token + channel_index; 159 | auto const lock = std::scoped_lock{channel_state.mutex()}; 160 | 161 | if constexpr (ChannelState::buff_size != 0) 162 | { 163 | if (not channel_state.buffer().empty()) 164 | { 165 | if (not claim(select_ctx)) 166 | { 167 | // A different waiting operation succeeded concurrently 168 | return true; 169 | } 170 | 171 | // Get a value from the buffer. 172 | channel_state.buffer().dequeue(slot_); 173 | 174 | if constexpr (not ChannelState::write_never_waits) 175 | { 176 | if (auto const writer = channel_state.writer_list().dequeue_first_available()) 177 | { 178 | // Buffer was full with writers waiting. 179 | // Wake the oldest writer and store his value in the buffer. 180 | channel_state.buffer().enqueue(*writer->slot); 181 | detail::notify_waiter(*writer); 182 | } 183 | } 184 | 185 | ready_alternative = channel_index; 186 | 187 | return true; 188 | } 189 | } 190 | else if (auto const writer = channel_state.writer_list().dequeue_first_available(select_ctx)) 191 | { 192 | // Get a value directly from a waiting writer. 193 | transfer(*writer->slot, slot_); 194 | detail::notify_waiter(*writer); 195 | ready_alternative = channel_index; 196 | 197 | return true; 198 | } 199 | 200 | // Wait for a value. 201 | auto& waiter_node = wait_state.waiter_nodes[channel_index].emplace(); 202 | waiter_node.ctx = &select_ctx; 203 | waiter_node.slot = &slot_; 204 | waiter_node.token = token; 205 | waiter_node.next = nullptr; 206 | 207 | channel_state.reader_list().enqueue(waiter_node); 208 | 209 | return false; 210 | }(std::get(channels_).shared_state()) 211 | or ...); 212 | 213 | return ready_alternative; 214 | }(std::index_sequence_for{})); 215 | } 216 | 217 | void clear_wait( 218 | std::optional const successful_alternative, 219 | wait_state_type& wait_state) 220 | { 221 | ([&](std::index_sequence) 222 | { 223 | ([&](auto& channel_state) 224 | { 225 | constexpr auto channel_index = indices; 226 | auto& waiter_node = wait_state.waiter_nodes[channel_index]; 227 | 228 | if (channel_index == successful_alternative or not waiter_node.has_value()) 229 | { 230 | // No need to clear wait on a successful or unsubmitted sub-operation 231 | return; 232 | } 233 | 234 | auto const lock = std::scoped_lock{channel_state.mutex()}; 235 | channel_state.reader_list().dequeue(*waiter_node); 236 | }(std::get(channels_).shared_state()), 237 | ...); 238 | }(std::index_sequence_for{})); 239 | } 240 | 241 | [[nodiscard]] auto get_result(std::size_t const successful_alternative) noexcept -> result_type 242 | { 243 | auto result = std::optional{}; 244 | 245 | ([&](std::index_sequence) 246 | { 247 | ([&](auto& channel) 248 | { 249 | constexpr auto channel_index = indices; 250 | 251 | if (successful_alternative == channel_index) 252 | { 253 | if constexpr (std::is_void_v) 254 | { 255 | result.emplace(channel); 256 | } 257 | else 258 | { 259 | result.emplace(slot_.read(), channel); 260 | } 261 | return true; 262 | } 263 | 264 | return false; 265 | }(std::get(channels_)) 266 | or ...); 267 | }(std::index_sequence_for{})); 268 | 269 | assert(result.has_value()); 270 | 271 | return std::move(*result); 272 | } 273 | 274 | private: 275 | std::tuple channels_; 276 | [[no_unique_address]] slot_type slot_; 277 | }; 278 | 279 | template 280 | read(ChannelsHead&, ChannelsTail&...) -> read; 281 | } // namespace ops 282 | } // namespace asiochan 283 | -------------------------------------------------------------------------------- /include/asiochan/select.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "asiochan/asio.hpp" 14 | #include "asiochan/async_promise.hpp" 15 | #include "asiochan/detail/channel_shared_state.hpp" 16 | #include "asiochan/detail/channel_waiter_list.hpp" 17 | #include "asiochan/detail/select_impl.hpp" 18 | #include "asiochan/detail/send_slot.hpp" 19 | #include "asiochan/select_concepts.hpp" 20 | #include "asiochan/select_result.hpp" 21 | 22 | namespace asiochan 23 | { 24 | // clang-format off 25 | template ::executor_type> 27 | requires waitable_selection 28 | [[nodiscard]] auto select(Ops... ops_args) 29 | -> asio::awaitable, Executor> 30 | // clang-format on 31 | { 32 | auto result = std::optional>{}; 33 | auto submit_mutex = std::mutex{}; 34 | auto wait_ctx = detail::select_wait_context{}; 35 | auto ops_wait_states = std::tuple{}; 36 | 37 | auto const success_token = co_await suspend_with_promise( 38 | [](async_promise&& promise, 39 | auto* const submit_mutex, 40 | auto* const wait_ctx, 41 | auto* const ops_wait_states, 42 | auto* const... ops_args) 43 | { 44 | wait_ctx->promise = std::move(promise); 45 | 46 | auto ready_token = std::optional{}; 47 | 48 | { 49 | auto const submit_lock = std::scoped_lock{*submit_mutex}; 50 | 51 | ([&](std::index_sequence) 52 | { 53 | ([&](auto& op, detail::constant) 54 | { 55 | constexpr auto op_base_token = detail::select_ops_base_tokens[channel_index]; 56 | 57 | if (auto const ready_alternative = op.submit_with_wait( 58 | *wait_ctx, 59 | op_base_token, 60 | std::get(*ops_wait_states))) 61 | { 62 | ready_token = op_base_token + *ready_alternative; 63 | 64 | return true; 65 | } 66 | 67 | return false; 68 | }(*ops_args, detail::constant{}) 69 | or ...); 70 | }(std::index_sequence_for{})); 71 | } 72 | 73 | if (ready_token) 74 | { 75 | wait_ctx->promise.set_value(*ready_token); 76 | } 77 | }, 78 | &submit_mutex, 79 | &wait_ctx, 80 | &ops_wait_states, 81 | &ops_args...); 82 | 83 | auto const submit_lock = std::scoped_lock{submit_mutex}; 84 | 85 | ([&](std::index_sequence) 86 | { 87 | ([&](Op& op, detail::constant) 88 | { 89 | constexpr auto op_base_token = detail::select_ops_base_tokens[channel_index]; 90 | 91 | auto successful_alternative = std::optional{}; 92 | 93 | if (success_token >= op_base_token 94 | and success_token < op_base_token + Op::num_alternatives) 95 | { 96 | successful_alternative = success_token - op_base_token; 97 | result.emplace(op.get_result(*successful_alternative), success_token); 98 | } 99 | 100 | op.clear_wait( 101 | successful_alternative, 102 | std::get(ops_wait_states)); 103 | }(ops_args, detail::constant{}), 104 | ...); 105 | }(std::index_sequence_for{})); 106 | 107 | assert(result.has_value()); 108 | 109 | co_return std::move(*result); 110 | } 111 | 112 | // clang-format off 113 | template 114 | requires waitfree_selection 115 | auto select_ready(Ops... ops_args) -> select_result 116 | // clang-format on 117 | { 118 | auto result = std::optional>{}; 119 | 120 | ([&](std::index_sequence) 121 | { 122 | ([&](auto& op, detail::constant) 123 | { 124 | constexpr auto op_base_token = detail::select_ops_base_tokens[channel_index]; 125 | 126 | if (auto const ready_alternative = op.submit_if_ready()) 127 | { 128 | result.emplace( 129 | op.get_result(*ready_alternative), 130 | op_base_token + *ready_alternative); 131 | 132 | return true; 133 | } 134 | 135 | return false; 136 | }(ops_args, detail::constant{}) 137 | or ...); 138 | }(std::index_sequence_for{})); 139 | 140 | assert(result.has_value()); 141 | 142 | return std::move(*result); 143 | } 144 | } // namespace asiochan 145 | -------------------------------------------------------------------------------- /include/asiochan/select_concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "asiochan/asio.hpp" 9 | #include "asiochan/detail/channel_waiter_list.hpp" 10 | #include "asiochan/detail/type_traits.hpp" 11 | 12 | namespace asiochan 13 | { 14 | enum class select_waitful_submit_result 15 | { 16 | waiting, 17 | completed_waitfree, 18 | }; 19 | 20 | // clang-format off 21 | template 22 | concept select_op = requires (T& op, T const& const_op, std::size_t const& successful_alternative) 23 | { 24 | typename T::executor_type; 25 | requires asio::execution::executor; 26 | 27 | typename T::result_type; 28 | typename std::integral_constant; 29 | typename std::bool_constant; 30 | 31 | { op.submit_if_ready() } 32 | -> std::same_as>; 33 | 34 | { op.get_result(successful_alternative) } 35 | -> std::same_as; 36 | }; 37 | 38 | template 39 | concept waitfree_select_op = select_op and T::always_waitfree; 40 | 41 | template 42 | concept waitable_select_op 43 | = select_op 44 | and not waitfree_select_op 45 | and requires ( 46 | T& op, 47 | detail::select_wait_context& select_ctx, 48 | detail::select_waiter_token const& base_token, 49 | std::optional const& successful_alternative) 50 | { 51 | typename T::wait_state_type; 52 | requires std::default_initializable; 53 | 54 | requires requires (typename T::wait_state_type& wait_state) 55 | { 56 | { op.submit_with_wait(select_ctx, base_token, wait_state) } 57 | -> std::same_as>; 58 | 59 | op.clear_wait(successful_alternative, wait_state); 60 | }; 61 | }; 62 | 63 | template 64 | concept waitfree_selection 65 | = (sizeof...(Ops) >= 1u) 66 | and ((static_cast(waitable_select_op) + ...) == sizeof...(Ops) - 1u) 67 | and waitfree_select_op>; 68 | 69 | template 70 | concept waitable_selection 71 | = (sizeof...(Ops) >= 1u) 72 | and (waitable_select_op and ...); 73 | // clang-format on 74 | } // namespace asiochan 75 | -------------------------------------------------------------------------------- /include/asiochan/select_result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "asiochan/channel_concepts.hpp" 10 | #include "asiochan/detail/overloaded.hpp" 11 | #include "asiochan/nothing_op.hpp" 12 | #include "asiochan/read_op.hpp" 13 | #include "asiochan/select_concepts.hpp" 14 | #include "asiochan/write_op.hpp" 15 | 16 | namespace asiochan 17 | { 18 | class bad_select_result_access final : public std::exception 19 | { 20 | public: 21 | [[nodiscard]] auto what() const noexcept -> char const* override 22 | { 23 | return "bad select result access"; 24 | } 25 | }; 26 | 27 | template 28 | class select_result 29 | { 30 | public: 31 | using variant_type = std::variant; 32 | using discriminator_type = std::size_t; 33 | 34 | template 35 | static constexpr bool is_alternative = (std::same_as or ...); 36 | 37 | template T> 38 | select_result(T&& value, discriminator_type const alternative) 39 | : result_{std::forward(value)} 40 | , alternative_{alternative} 41 | { 42 | } 43 | 44 | [[nodiscard]] friend auto operator==( 45 | select_result const& lhs, 46 | select_result const& rhs) noexcept -> bool 47 | = default; 48 | 49 | [[nodiscard]] friend auto operator<=>( 50 | select_result const& lhs, 51 | select_result const& rhs) noexcept = default; 52 | 53 | [[nodiscard]] auto to_variant() && noexcept -> variant_type 54 | { 55 | return std::move(result_); 56 | } 57 | 58 | [[nodiscard]] auto alternative() const noexcept -> discriminator_type 59 | { 60 | return alternative_; 61 | } 62 | 63 | // clang-format off 64 | template 65 | requires is_alternative 66 | [[nodiscard]] auto is() const noexcept -> bool 67 | // clang-format on 68 | { 69 | // std::holds_alternative does not support multiple type occurrence 70 | return std::visit( 71 | detail::overloaded{ 72 | [](T const&) 73 | { return true; }, 74 | [](auto const&) 75 | { return false; }, 76 | }, 77 | result_); 78 | } 79 | 80 | // clang-format off 81 | template 82 | requires is_alternative> 83 | [[nodiscard]] auto received() const noexcept -> bool 84 | // clang-format on 85 | { 86 | return is>(); 87 | } 88 | 89 | // clang-format off 90 | template 91 | requires is_alternative> 92 | [[nodiscard]] auto sent() const noexcept -> bool 93 | // clang-format on 94 | { 95 | return is>(); 96 | } 97 | 98 | // clang-format off 99 | [[nodiscard]] auto has_value() const noexcept -> bool 100 | requires is_alternative 101 | // clang-format on 102 | { 103 | return not is(); 104 | } 105 | 106 | // clang-format off 107 | explicit operator bool() const noexcept 108 | requires is_alternative 109 | // clang-format on 110 | { 111 | return has_value(); 112 | } 113 | 114 | // clang-format off 115 | template 116 | requires is_alternative> 117 | or is_alternative> 118 | [[nodiscard]] auto matches(T const& channel) const noexcept -> bool 119 | // clang-format on 120 | { 121 | return std::visit( 122 | [&](auto const& result) 123 | { return result.matches(channel); }, 124 | result_); 125 | } 126 | 127 | // clang-format off 128 | template 129 | requires is_alternative> 130 | [[nodiscard]] auto received_from(T const& channel) const noexcept -> bool 131 | // clang-format on 132 | { 133 | using SendType = typename T::send_type; 134 | 135 | return std::visit( 136 | detail::overloaded{ 137 | [&](read_result const& result) 138 | { return result.matches(channel); }, 139 | [](auto const&) 140 | { return false; }, 141 | }, 142 | result_); 143 | } 144 | 145 | // clang-format off 146 | template 147 | requires is_alternative> 148 | [[nodiscard]] auto sent_to(T const& channel) const noexcept -> bool 149 | // clang-format on 150 | { 151 | using SendType = typename T::send_type; 152 | 153 | return std::visit( 154 | detail::overloaded{ 155 | [&](write_result const& result) 156 | { return result.matches(channel); }, 157 | [](auto const&) 158 | { return false; }, 159 | }, 160 | result_); 161 | } 162 | 163 | // clang-format off 164 | template 165 | requires is_alternative 166 | [[nodiscard]] auto get() & -> T& 167 | // clang-format on 168 | { 169 | return std::visit( 170 | detail::overloaded{ 171 | [](T& result) -> T& { return result; }, 172 | [](auto const&) -> T& { throw bad_select_result_access{}; }, 173 | }, 174 | result_); 175 | } 176 | 177 | // clang-format off 178 | template 179 | requires is_alternative 180 | [[nodiscard]] auto get() const& -> T const& 181 | // clang-format on 182 | { 183 | return std::visit( 184 | detail::overloaded{ 185 | [](T const& result) -> T const& { return result; }, 186 | [](auto const&) -> T const& { throw bad_select_result_access{}; }, 187 | }, 188 | result_); 189 | } 190 | 191 | // clang-format off 192 | template 193 | requires is_alternative 194 | [[nodiscard]] auto get() && -> T&& 195 | // clang-format on 196 | { 197 | return std::visit( 198 | detail::overloaded{ 199 | [](T& result) -> T&& { return std::move(result); }, 200 | [](auto const&) -> T&& { throw bad_select_result_access{}; }, 201 | }, 202 | result_); 203 | } 204 | 205 | // clang-format off 206 | template 207 | requires is_alternative 208 | [[nodiscard]] auto get() const&& -> T const&& 209 | // clang-format on 210 | { 211 | return std::visit( 212 | detail::overloaded{ 213 | [](T const& result) -> T const&& { return std::move(result); }, 214 | [](auto const&) -> T const&& { throw bad_select_result_access{}; }, 215 | }, 216 | result_); 217 | } 218 | 219 | // clang-format off 220 | template 221 | requires is_alternative> 222 | [[nodiscard]] auto get_received() & -> T& 223 | // clang-format on 224 | { 225 | return get>().get(); 226 | } 227 | 228 | // clang-format off 229 | template 230 | requires is_alternative> 231 | [[nodiscard]] auto get_received() const& -> T const& 232 | // clang-format on 233 | { 234 | return get>().get(); 235 | } 236 | 237 | // clang-format off 238 | template 239 | requires is_alternative> 240 | [[nodiscard]] auto get_received()&& -> T&& 241 | // clang-format on 242 | { 243 | return std::move(get>().get()); 244 | } 245 | 246 | // clang-format off 247 | template 248 | requires is_alternative> 249 | [[nodiscard]] auto get_received() const&& -> T const&& 250 | // clang-format on 251 | { 252 | return std::move(get>().get()); 253 | } 254 | 255 | // clang-format off 256 | template 257 | requires is_alternative 258 | [[nodiscard]] auto get_if() noexcept -> T* 259 | // clang-format on 260 | { 261 | return std::visit( 262 | detail::overloaded{ 263 | [](T& result) -> T* { return &result; }, 264 | [](auto const&) -> T* { return nullptr; }, 265 | }, 266 | result_); 267 | } 268 | 269 | // clang-format off 270 | template 271 | requires is_alternative 272 | [[nodiscard]] auto get_if() const noexcept -> T const* 273 | // clang-format on 274 | { 275 | return std::visit( 276 | detail::overloaded{ 277 | [](T const& result) -> T const* { return &result; }, 278 | [](auto const&) -> T const* { return nullptr; }, 279 | }, 280 | result_); 281 | } 282 | 283 | // clang-format off 284 | template 285 | requires is_alternative> 286 | [[nodiscard]] auto get_if_received() noexcept -> T* 287 | // clang-format on 288 | { 289 | if (auto const result = get_if>()) 290 | { 291 | return &result->get(); 292 | } 293 | return nullptr; 294 | } 295 | 296 | // clang-format off 297 | template 298 | requires is_alternative> 299 | [[nodiscard]] auto get_if_received() const noexcept -> T const* 300 | // clang-format on 301 | { 302 | if (auto const result = get_if>()) 303 | { 304 | return &result->get(); 305 | } 306 | return nullptr; 307 | } 308 | 309 | // clang-format off 310 | template 311 | requires is_alternative> 312 | and sendable_value 313 | [[nodiscard]] auto get_if_received_from(T const& channel) noexcept 314 | -> typename T::send_type* 315 | // clang-format on 316 | { 317 | if (auto const result = get_if>(); 318 | result and result->matches(channel)) 319 | { 320 | return &result->get(); 321 | } 322 | return nullptr; 323 | } 324 | 325 | // clang-format off 326 | template 327 | requires is_alternative> 328 | and sendable_value 329 | [[nodiscard]] auto get_if_received_from(T const& channel) const noexcept 330 | -> typename T::send_type const* 331 | // clang-format on 332 | { 333 | if (auto const result = get_if>(); 334 | result and result->matches(channel)) 335 | { 336 | return &result->get(); 337 | } 338 | return nullptr; 339 | } 340 | 341 | private: 342 | variant_type result_; 343 | discriminator_type alternative_; 344 | }; 345 | } // namespace asiochan 346 | -------------------------------------------------------------------------------- /include/asiochan/sendable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace asiochan 7 | { 8 | // clang-format off 9 | template 10 | concept sendable_value 11 | = (not std::is_reference_v) 12 | and std::is_nothrow_move_constructible_v 13 | and std::is_nothrow_move_assignable_v; 14 | 15 | template 16 | concept sendable = sendable_value or std::is_void_v; 17 | // clang-format on 18 | } // namespace asiochan 19 | -------------------------------------------------------------------------------- /include/asiochan/write_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "asiochan/asio.hpp" 11 | #include "asiochan/channel_concepts.hpp" 12 | #include "asiochan/detail/channel_op_result_base.hpp" 13 | #include "asiochan/detail/channel_waiter_list.hpp" 14 | #include "asiochan/detail/send_slot.hpp" 15 | #include "asiochan/select_concepts.hpp" 16 | #include "asiochan/sendable.hpp" 17 | 18 | namespace asiochan 19 | { 20 | template 21 | class write_result : public detail::channel_op_result_base 22 | { 23 | private: 24 | using base = detail::channel_op_result_base; 25 | 26 | public: 27 | using base::base; 28 | }; 29 | 30 | namespace ops 31 | { 32 | template ChannelsHead, writable_channel_type... ChannelsTail> 33 | class write 34 | { 35 | private: 36 | static constexpr auto num_always_waitfree = [](std::type_identity...) 37 | { 38 | return (static_cast(ChannelTypes::shared_state_type::write_never_waits) + ...); 39 | } 40 | (std::type_identity{}, 41 | std::type_identity{}...); 42 | static constexpr auto last_always_waitfree 43 | = detail::last_t::shared_state_type::write_never_waits; 44 | 45 | static_assert( 46 | num_always_waitfree == 0 or (num_always_waitfree == 1 and last_always_waitfree), 47 | "Only the last target channel of a write operation can be unbounded"); 48 | 49 | public: 50 | using executor_type = typename ChannelsHead::executor_type; 51 | using result_type = write_result; 52 | using slot_type = detail::send_slot; 53 | using waiter_node_type = detail::channel_waiter_list_node; 54 | 55 | static constexpr auto num_alternatives = sizeof...(ChannelsTail) + 1u; 56 | static constexpr auto always_waitfree = last_always_waitfree; 57 | 58 | struct wait_state_type 59 | { 60 | std::array, num_alternatives> waiter_nodes = {}; 61 | }; 62 | 63 | // clang-format off 64 | template U> 65 | requires sendable_value 66 | write(U&& value, ChannelsHead& channels_head, ChannelsTail&... channels_tail) noexcept 67 | // clang-format on 68 | : channels_{channels_head, channels_tail...} 69 | { 70 | slot_.write(T{std::forward(value)}); 71 | } 72 | 73 | // clang-format off 74 | explicit write(ChannelsHead& channels_head, ChannelsTail&... channels_tail) noexcept 75 | requires std::is_void_v 76 | // clang-format on 77 | : channels_{channels_head, channels_tail...} 78 | { 79 | } 80 | 81 | [[nodiscard]] auto submit_if_ready() -> std::optional 82 | { 83 | auto ready_alternative = std::optional{}; 84 | 85 | ([&](std::index_sequence) 86 | { 87 | ([&](ChannelState& channel_state) 88 | { 89 | constexpr auto channel_index = indices; 90 | auto const lock = std::scoped_lock{channel_state.mutex()}; 91 | 92 | if (auto const reader = channel_state.reader_list().dequeue_first_available()) 93 | { 94 | // Buffer was empty with readers waiting. 95 | // Wake the oldest reader and give him a value. 96 | transfer(slot_, *reader->slot); 97 | detail::notify_waiter(*reader); 98 | ready_alternative = channel_index; 99 | 100 | return true; 101 | } 102 | else if constexpr (ChannelState::buff_size != 0) 103 | { 104 | if (not channel_state.buffer().full()) 105 | { 106 | // Store the value in the buffer. 107 | channel_state.buffer().enqueue(slot_); 108 | ready_alternative = channel_index; 109 | 110 | return true; 111 | } 112 | } 113 | 114 | return false; 115 | }(std::get(channels_).shared_state()) 116 | or ...); 117 | }(std::index_sequence_for{})); 118 | 119 | return ready_alternative; 120 | } 121 | 122 | // clang-format off 123 | [[nodiscard]] auto submit_with_wait( 124 | detail::select_wait_context& select_ctx, 125 | detail::select_waiter_token const base_token, 126 | wait_state_type& wait_state) 127 | -> std::optional 128 | requires (not always_waitfree) 129 | // clang-format on 130 | { 131 | return ([&](std::index_sequence) 132 | { 133 | auto ready_alternative = std::optional{}; 134 | 135 | ([&](ChannelState& channel_state) 136 | { 137 | constexpr auto channel_index = indices; 138 | auto const token = base_token + channel_index; 139 | auto const lock = std::scoped_lock{channel_state.mutex()}; 140 | 141 | if (auto const reader = channel_state.reader_list().dequeue_first_available(select_ctx)) 142 | { 143 | // Buffer was empty with readers waiting. 144 | // Wake the oldest reader and give him a value. 145 | transfer(slot_, *reader->slot); 146 | detail::notify_waiter(*reader); 147 | ready_alternative = channel_index; 148 | 149 | return true; 150 | } 151 | else if constexpr (ChannelState::buff_size != 0) 152 | { 153 | if (not channel_state.buffer().full()) 154 | { 155 | if (claim(select_ctx)) 156 | { 157 | // Store the value in the buffer. 158 | channel_state.buffer().enqueue(slot_); 159 | ready_alternative = channel_index; 160 | } 161 | 162 | return true; 163 | } 164 | } 165 | 166 | // Wait for a value. 167 | auto& waiter_node = wait_state.waiter_nodes[channel_index].emplace(); 168 | waiter_node.ctx = &select_ctx; 169 | waiter_node.slot = &slot_; 170 | waiter_node.token = token; 171 | waiter_node.next = nullptr; 172 | 173 | channel_state.writer_list().enqueue(waiter_node); 174 | 175 | return false; 176 | }(std::get(channels_).shared_state()) 177 | or ...); 178 | 179 | return ready_alternative; 180 | }(std::index_sequence_for{})); 181 | } 182 | 183 | // clang-format off 184 | void clear_wait( 185 | std::optional const successful_alternative, 186 | wait_state_type& wait_state) 187 | requires (not always_waitfree) 188 | // clang-format on 189 | { 190 | ([&](std::index_sequence) 191 | { 192 | ([&](auto& channel_state) 193 | { 194 | constexpr auto channel_index = indices; 195 | auto& waiter_node = wait_state.waiter_nodes[channel_index]; 196 | 197 | if (channel_index == successful_alternative or not waiter_node.has_value()) 198 | { 199 | // No need to clear wait on a successful or unsubmitted sub-operation 200 | return; 201 | } 202 | 203 | auto const lock = std::scoped_lock{channel_state.mutex()}; 204 | channel_state.reader_list().dequeue(*waiter_node); 205 | }(std::get(channels_).shared_state()), 206 | ...); 207 | }(std::index_sequence_for{})); 208 | } 209 | 210 | [[nodiscard]] auto get_result(std::size_t const successful_alternative) noexcept -> result_type 211 | { 212 | auto result = std::optional{}; 213 | 214 | ([&](std::index_sequence) 215 | { 216 | ([&](auto& channel) 217 | { 218 | constexpr auto channel_index = indices; 219 | 220 | if (successful_alternative == channel_index) 221 | { 222 | result.emplace(channel); 223 | return true; 224 | } 225 | 226 | return false; 227 | }(std::get(channels_)) 228 | or ...); 229 | }(std::index_sequence_for{})); 230 | 231 | assert(result.has_value()); 232 | 233 | return std::move(*result); 234 | } 235 | 236 | private: 237 | std::tuple channels_; 238 | [[no_unique_address]] slot_type slot_; 239 | }; 240 | 241 | // clang-format off 242 | template 243 | requires(not std::same_as) 244 | write(U&&, ChannelsHead&, ChannelsTail&...) -> write; 245 | // clang-format on 246 | 247 | template ChannelsHead, channel_type... ChannelsTail> 248 | write(ChannelsHead&, ChannelsTail&...) -> write; 249 | } // namespace ops 250 | } // namespace asiochan 251 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(asiochan_tests) 2 | add_test( 3 | NAME asiochan_tests 4 | COMMAND asiochan_tests 5 | ) 6 | target_include_directories( 7 | asiochan_tests 8 | 9 | PRIVATE 10 | "${CMAKE_CURRENT_SOURCE_DIR}" 11 | "${CMAKE_CURRENT_BINARY_DIR}" 12 | ) 13 | target_link_libraries( 14 | asiochan_tests 15 | 16 | PRIVATE 17 | CONAN_PKG::catch2 18 | Threads::Threads 19 | asiochan::asiochan 20 | ) 21 | 22 | add_subdirectory(asiochan) 23 | -------------------------------------------------------------------------------- /tests/asiochan/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources( 2 | asiochan_tests 3 | 4 | PRIVATE 5 | test_channel.cpp 6 | test_main.cpp 7 | ) 8 | -------------------------------------------------------------------------------- /tests/asiochan/test_channel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #ifdef ASIOCHAN_USE_STANDALONE_ASIO 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #else 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #endif 25 | 26 | namespace asio = asiochan::asio; 27 | 28 | TEST_CASE("Channels") 29 | { 30 | auto const num_threads = GENERATE(range(1u, 2u)); 31 | auto thread_pool = asio::thread_pool{num_threads}; 32 | 33 | SECTION("Ping-pong") 34 | { 35 | auto channel = asiochan::channel{}; 36 | 37 | auto ping_task = asio::co_spawn( 38 | thread_pool, 39 | [channel]() mutable -> asio::awaitable 40 | { 41 | co_await channel.write("ping"); 42 | auto const recv = co_await channel.read(); 43 | CHECK(recv == "pong"); 44 | }, 45 | asio::use_future); 46 | 47 | auto pong_task = asio::co_spawn( 48 | thread_pool, 49 | [channel]() mutable -> asio::awaitable 50 | { 51 | auto const recv = co_await channel.read(); 52 | CHECK(recv == "ping"); 53 | co_await channel.write("pong"); 54 | }, 55 | asio::use_future); 56 | 57 | pong_task.get(); 58 | ping_task.get(); 59 | } 60 | 61 | SECTION("Buffered channel") 62 | { 63 | static constexpr auto buffer_size = 3; 64 | 65 | auto channel = asiochan::channel{}; 66 | auto read_channel = asiochan::read_channel{channel}; 67 | auto write_channel = asiochan::write_channel{channel}; 68 | 69 | for (auto const i : std::views::iota(0, buffer_size)) 70 | { 71 | auto const was_sent = write_channel.try_write(i); 72 | CHECK(was_sent); 73 | } 74 | auto const last_was_sent = write_channel.try_write(0); 75 | CHECK(not last_was_sent); 76 | 77 | for (auto const i : std::views::iota(0, buffer_size)) 78 | { 79 | auto const recv = read_channel.try_read(); 80 | REQUIRE(recv.has_value()); 81 | CHECK(*recv == i); 82 | } 83 | auto const last_recv = read_channel.try_read(); 84 | CHECK(not last_recv.has_value()); 85 | } 86 | 87 | SECTION("Buffered channel of void") 88 | { 89 | static constexpr auto buffer_size = 3; 90 | 91 | auto channel = asiochan::channel{}; 92 | auto read_channel = asiochan::read_channel{channel}; 93 | auto write_channel = asiochan::write_channel{channel}; 94 | 95 | for (auto const i : std::views::iota(0, buffer_size)) 96 | { 97 | auto const was_sent = write_channel.try_write(); 98 | CHECK(was_sent); 99 | } 100 | auto const last_was_sent = write_channel.try_write(); 101 | CHECK(not last_was_sent); 102 | 103 | for (auto const i : std::views::iota(0, buffer_size)) 104 | { 105 | auto const recv = read_channel.try_read(); 106 | CHECK(recv); 107 | } 108 | auto const last_recv = read_channel.try_read(); 109 | CHECK(not last_recv); 110 | } 111 | 112 | SECTION("Unbounded buffered channel") 113 | { 114 | static constexpr auto num_tokens = 10; 115 | 116 | auto channel = asiochan::unbounded_channel{}; 117 | auto read_channel = asiochan::unbounded_read_channel{channel}; 118 | auto write_channel = asiochan::unbounded_write_channel{channel}; 119 | 120 | for (auto const i : std::views::iota(0, num_tokens)) 121 | { 122 | write_channel.write(i); 123 | } 124 | 125 | for (auto const i : std::views::iota(0, num_tokens)) 126 | { 127 | auto const recv = read_channel.try_read(); 128 | CHECK(recv == i); 129 | } 130 | auto const last_recv = read_channel.try_read(); 131 | CHECK(not last_recv.has_value()); 132 | } 133 | 134 | SECTION("Multiple writers and receivers") 135 | { 136 | static constexpr auto num_tokens_per_task = 5; 137 | static constexpr auto num_tasks = 3; 138 | 139 | auto channel = asiochan::channel{}; 140 | auto read_channel = asiochan::read_channel{channel}; 141 | auto write_channel = asiochan::write_channel{channel}; 142 | 143 | auto source_values = std::vector(num_tasks * num_tokens_per_task); 144 | std::iota(source_values.begin(), source_values.end(), 0); 145 | 146 | auto source_tasks = std::vector>{}; 147 | for (auto const task_id : std::views::iota(0, num_tasks)) 148 | { 149 | source_tasks.push_back( 150 | asio::co_spawn( 151 | thread_pool, 152 | [write_channel, task_id, &source_values]() mutable -> asio::awaitable 153 | { 154 | auto const start = task_id * num_tokens_per_task; 155 | for (auto const i : std::views::iota(start, start + num_tokens_per_task)) 156 | { 157 | co_await write_channel.write(source_values[i]); 158 | } 159 | }, 160 | asio::use_future)); 161 | } 162 | 163 | auto sink_values = std::vector(num_tasks * num_tokens_per_task); 164 | auto sink_tasks = std::vector>{}; 165 | for (auto const task_id : std::views::iota(0, num_tasks)) 166 | { 167 | sink_tasks.push_back( 168 | asio::co_spawn( 169 | thread_pool, 170 | [read_channel, task_id, &sink_values]() mutable -> asio::awaitable 171 | { 172 | auto const start = task_id * num_tokens_per_task; 173 | for (auto const i : std::views::iota(start, start + num_tokens_per_task)) 174 | { 175 | sink_values[i] = co_await read_channel.read(); 176 | } 177 | }, 178 | asio::use_future)); 179 | } 180 | 181 | for (auto& sink_task : sink_tasks) 182 | { 183 | sink_task.get(); 184 | } 185 | 186 | std::ranges::sort(sink_values); 187 | CHECK(source_values == sink_values); 188 | 189 | for (auto& source_task : source_tasks) 190 | { 191 | source_task.get(); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/asiochan/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | --------------------------------------------------------------------------------