100.00% Lines (36/36)
100.00% Functions (13/13)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | |||||
| 3 | // | 3 | // | |||||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 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) | 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 6 | // | 6 | // | |||||
| 7 | // Official repository: https://github.com/cppalliance/capy | 7 | // Official repository: https://github.com/cppalliance/capy | |||||
| 8 | // | 8 | // | |||||
| 9 | 9 | |||||||
| 10 | #ifndef BOOST_CAPY_IO_TASK_HPP | 10 | #ifndef BOOST_CAPY_IO_TASK_HPP | |||||
| 11 | #define BOOST_CAPY_IO_TASK_HPP | 11 | #define BOOST_CAPY_IO_TASK_HPP | |||||
| 12 | 12 | |||||||
| 13 | #include <boost/capy/io_result.hpp> | 13 | #include <boost/capy/io_result.hpp> | |||||
| 14 | #include <boost/capy/task.hpp> | 14 | #include <boost/capy/task.hpp> | |||||
| 15 | + | #include <coroutine> | ||||||
| 15 | 16 | |||||||
| 16 | namespace boost { | 17 | namespace boost { | |||||
| 17 | namespace capy { | 18 | namespace capy { | |||||
| 18 | 19 | |||||||
| 19 | /** A task type for I/O operations yielding io_result. | 20 | /** A task type for I/O operations yielding io_result. | |||||
| 20 | 21 | |||||||
| 21 | This is a convenience alias for `task<io_result<Ts...>>`. | 22 | This is a convenience alias for `task<io_result<Ts...>>`. | |||||
| 22 | The converting constructor on `io_result<>` allows direct | 23 | The converting constructor on `io_result<>` allows direct | |||||
| 23 | `co_return` of error codes: | 24 | `co_return` of error codes: | |||||
| 24 | 25 | |||||||
| 25 | @code | 26 | @code | |||||
| 26 | io_task<> connect_to_server(socket& s, endpoint ep) | 27 | io_task<> connect_to_server(socket& s, endpoint ep) | |||||
| 27 | { | 28 | { | |||||
| 28 | co_return co_await s.connect(ep); // returns io_result<> | 29 | co_return co_await s.connect(ep); // returns io_result<> | |||||
| 29 | } | 30 | } | |||||
| 30 | 31 | |||||||
| 31 | io_task<> handler(route_params& rp) | 32 | io_task<> handler(route_params& rp) | |||||
| 32 | { | 33 | { | |||||
| 33 | co_return route::next; // error_code converts to io_result<> | 34 | co_return route::next; // error_code converts to io_result<> | |||||
| 34 | } | 35 | } | |||||
| 35 | @endcode | 36 | @endcode | |||||
| 36 | 37 | |||||||
| 37 | @tparam Ts Additional value types beyond error_code. | 38 | @tparam Ts Additional value types beyond error_code. | |||||
| 38 | */ | 39 | */ | |||||
| 39 | template<class... Ts> | 40 | template<class... Ts> | |||||
| 40 | - | using io_task = task<io_result<Ts...>>; | 41 | + | struct io_task | |||
| 42 | + | { | ||||||
| 43 | + | struct promise_type : task<io_result<Ts...>>::promise_type | ||||||
| 44 | + | { | ||||||
| HITGNC | 45 | + | 1614 | io_task get_return_object() | ||||
| 46 | + | { | ||||||
| HITGNC | 47 | + | 1614 | return io_task{std::coroutine_handle<promise_type>::from_promise(*this)}; | ||||
| 48 | + | } | ||||||
| 49 | + | |||||||
| 50 | + | friend io_task; | ||||||
| 51 | + | |||||||
| 52 | + | // An io_task can yield an io_result value. That means the coroutie suspend if the error is set. | ||||||
| 53 | + | template<typename ... Us> | ||||||
| 54 | + | requires (std::constructible_from<Ts> && ...) | ||||||
| HITGNC | 55 | + | 6 | auto yield_value(io_result<Us...> res) | ||||
| 56 | + | { | ||||||
| 57 | + | struct awaiter | ||||||
| 58 | + | { | ||||||
| 59 | + | io_result<Us...> res; | ||||||
| HITGNC | 60 | + | 6 | bool await_ready() {return !res.ec;} | ||||
| 61 | + | |||||||
| HITGNC | 62 | + | 2 | std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h) | ||||
| 63 | + | { | ||||||
| HITGNC | 64 | + | 2 | auto &p = h.promise(); | ||||
| HITGNC | 65 | + | 2 | p.return_value({res.ec, Ts()...}); | ||||
| 66 | + | |||||||
| HITGNC | 67 | + | 2 | return p.continuation(); | ||||
| 68 | + | } | ||||||
| 69 | + | |||||||
| HITGNC | 70 | + | 4 | std::tuple<Us...> await_resume() | ||||
| 71 | + | { | ||||||
| HITGNC | 72 | + | 4 | return std::move(res.values); | ||||
| 73 | + | } | ||||||
| 74 | + | }; | ||||||
| 75 | + | |||||||
| HITGNC | 76 | + | 6 | return awaiter{std::move(res)}; | ||||
| 77 | + | } | ||||||
| 78 | + | |||||||
| 79 | + | |||||||
| 80 | + | }; | ||||||
| 81 | + | |||||||
| 82 | + | /// Destroy the task and its coroutine frame if owned. | ||||||
| 83 | + | |||||||
| HITGNC | 84 | + | 4056 | ~io_task() | ||||
| 85 | + | { | ||||||
| HITGNC | 86 | + | 4056 | if (h_) | ||||
| HITGNC | 87 | + | 1607 | h_.destroy(); | ||||
| HITGNC | 88 | + | 4056 | } | ||||
| 89 | + | /// Return false; tasks are never immediately ready. | ||||||
| HITGNC | 90 | + | 1481 | bool await_ready() const noexcept | ||||
| 91 | + | { | ||||||
| HITGNC | 92 | + | 1481 | return false; | ||||
| 93 | + | } | ||||||
| 94 | + | |||||||
| 95 | + | /// Return the result or rethrow any stored exception. | ||||||
| HITGNC | 96 | + | 1607 | auto await_resume() | ||||
| 97 | + | { | ||||||
| HITGNC | 98 | + | 1607 | if(h_.promise().has_ep_) | ||||
| HITGNC | 99 | + | 533 | std::rethrow_exception(h_.promise().ep_); | ||||
| HITGNC | 100 | + | 1074 | return std::move(*h_.promise().result_); | ||||
| 101 | + | } | ||||||
| 102 | + | |||||||
| 103 | + | |||||||
| 104 | + | |||||||
| 105 | + | /// Start execution with the caller's context. | ||||||
| HITGNC | 106 | + | 1607 | std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) | ||||
| 107 | + | { | ||||||
| HITGNC | 108 | + | 1607 | h_.promise().set_continuation(cont); | ||||
| HITGNC | 109 | + | 1607 | h_.promise().set_environment(env); | ||||
| HITGNC | 110 | + | 1607 | return h_; | ||||
| 111 | + | } | ||||||
| 112 | + | |||||||
| 113 | + | /** Return the coroutine handle. | ||||||
| 114 | + | |||||||
| 115 | + | @note Do not call `destroy()` on the returned handle while the | ||||||
| 116 | + | task is being awaited. The task's lifetime is normally managed | ||||||
| 117 | + | by `run_async`, `run`, or the awaiting parent; manually | ||||||
| 118 | + | destroying a suspended task that another coroutine is awaiting | ||||||
| 119 | + | produces undefined behavior. For cooperative cancellation, use | ||||||
| 120 | + | `std::stop_token`. | ||||||
| 121 | + | |||||||
| 122 | + | @return The coroutine handle. | ||||||
| 123 | + | */ | ||||||
| HITGNC | 124 | + | 7 | std::coroutine_handle<promise_type> handle() const noexcept | ||||
| 125 | + | { | ||||||
| HITGNC | 126 | + | 7 | return h_; | ||||
| 127 | + | } | ||||||
| 128 | + | |||||||
| 129 | + | /** Release ownership of the coroutine frame. | ||||||
| 130 | + | |||||||
| 131 | + | After calling this, destroying the task does not destroy the | ||||||
| 132 | + | coroutine frame. The caller becomes responsible for the frame's | ||||||
| 133 | + | lifetime. | ||||||
| 134 | + | |||||||
| 135 | + | @note If the caller intends to call `destroy()` on the | ||||||
| 136 | + | released handle, it must do so only when the task has not | ||||||
| 137 | + | started or has fully completed. Destroying a suspended task | ||||||
| 138 | + | that is being awaited produces undefined behavior. | ||||||
| 139 | + | |||||||
| 140 | + | @par Postconditions | ||||||
| 141 | + | `handle()` returns the original handle, but the task no longer | ||||||
| 142 | + | owns it. | ||||||
| 143 | + | */ | ||||||
| HITGNC | 144 | + | 7 | void release() noexcept | ||||
| 145 | + | { | ||||||
| HITGNC | 146 | + | 7 | h_ = nullptr; | ||||
| HITGNC | 147 | + | 7 | } | ||||
| 148 | + | |||||||
| 149 | + | io_task(io_task const&) = delete; | ||||||
| 150 | + | io_task& operator=(io_task const&) = delete; | ||||||
| 151 | + | |||||||
| 152 | + | /// Construct by moving, transferring ownership. | ||||||
| HITGNC | 153 | + | 2442 | io_task(io_task&& other) noexcept | ||||
| HITGNC | 154 | + | 2442 | : h_(std::exchange(other.h_, nullptr)) | ||||
| 155 | + | { | ||||||
| HITGNC | 156 | + | 2442 | } | ||||
| 157 | + | |||||||
| 158 | + | /// Assign by moving, transferring ownership. | ||||||
| 159 | + | io_task& operator=(io_task&& other) noexcept | ||||||
| 160 | + | { | ||||||
| 161 | + | if(this != &other) | ||||||
| 162 | + | { | ||||||
| 163 | + | if(h_) | ||||||
| 164 | + | h_.destroy(); | ||||||
| 165 | + | h_ = std::exchange(other.h_, nullptr); | ||||||
| 166 | + | } | ||||||
| 167 | + | return *this; | ||||||
| 168 | + | } | ||||||
| 169 | + | |||||||
| 170 | + | |||||||
| 171 | + | private: | ||||||
| HITGNC | 172 | + | 1614 | explicit io_task(std::coroutine_handle<promise_type> h) | ||||
| HITGNC | 173 | + | 1614 | : h_(h) | ||||
| 174 | + | { | ||||||
| HITGNC | 175 | + | 1614 | } | ||||
| 176 | + | |||||||
| 177 | + | std::coroutine_handle<promise_type> h_; | ||||||
| 178 | + | |||||||
| 179 | + | |||||||
| 180 | + | }; | ||||||
| 41 | 181 | |||||||
| 42 | } // namespace capy | 182 | } // namespace capy | |||||
| 43 | } // namespace boost | 183 | } // namespace boost | |||||
| 44 | 184 | |||||||
| 45 | #endif | 185 | #endif | |||||