TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_IO_TASK_HPP
11 : #define BOOST_CAPY_IO_TASK_HPP
12 :
13 : #include <boost/capy/io_result.hpp>
14 : #include <boost/capy/task.hpp>
15 : #include <coroutine>
16 :
17 : namespace boost {
18 : namespace capy {
19 :
20 : /** A task type for I/O operations yielding io_result.
21 :
22 : This is a convenience alias for `task<io_result<Ts...>>`.
23 : The converting constructor on `io_result<>` allows direct
24 : `co_return` of error codes:
25 :
26 : @code
27 : io_task<> connect_to_server(socket& s, endpoint ep)
28 : {
29 : co_return co_await s.connect(ep); // returns io_result<>
30 : }
31 :
32 : io_task<> handler(route_params& rp)
33 : {
34 : co_return route::next; // error_code converts to io_result<>
35 : }
36 : @endcode
37 :
38 : @tparam Ts Additional value types beyond error_code.
39 : */
40 : template<class... Ts>
41 : struct io_task
42 : {
43 : struct promise_type : task<io_result<Ts...>>::promise_type
44 : {
45 HIT 1614 : io_task get_return_object()
46 : {
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> && ...)
55 6 : auto yield_value(io_result<Us...> res)
56 : {
57 : struct awaiter
58 : {
59 : io_result<Us...> res;
60 6 : bool await_ready() {return !res.ec;}
61 :
62 2 : std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h)
63 : {
64 2 : auto &p = h.promise();
65 2 : p.return_value({res.ec, Ts()...});
66 :
67 2 : return p.continuation();
68 : }
69 :
70 4 : std::tuple<Us...> await_resume()
71 : {
72 4 : return std::move(res.values);
73 : }
74 : };
75 :
76 6 : return awaiter{std::move(res)};
77 : }
78 :
79 :
80 : };
81 :
82 : /// Destroy the task and its coroutine frame if owned.
83 :
84 4056 : ~io_task()
85 : {
86 4056 : if (h_)
87 1607 : h_.destroy();
88 4056 : }
89 : /// Return false; tasks are never immediately ready.
90 1481 : bool await_ready() const noexcept
91 : {
92 1481 : return false;
93 : }
94 :
95 : /// Return the result or rethrow any stored exception.
96 1607 : auto await_resume()
97 : {
98 1607 : if(h_.promise().has_ep_)
99 533 : std::rethrow_exception(h_.promise().ep_);
100 1074 : return std::move(*h_.promise().result_);
101 : }
102 :
103 :
104 :
105 : /// Start execution with the caller's context.
106 1607 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
107 : {
108 1607 : h_.promise().set_continuation(cont);
109 1607 : h_.promise().set_environment(env);
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 : */
124 7 : std::coroutine_handle<promise_type> handle() const noexcept
125 : {
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 : */
144 7 : void release() noexcept
145 : {
146 7 : h_ = nullptr;
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.
153 2442 : io_task(io_task&& other) noexcept
154 2442 : : h_(std::exchange(other.h_, nullptr))
155 : {
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:
172 1614 : explicit io_task(std::coroutine_handle<promise_type> h)
173 1614 : : h_(h)
174 : {
175 1614 : }
176 :
177 : std::coroutine_handle<promise_type> h_;
178 :
179 :
180 : };
181 :
182 : } // namespace capy
183 : } // namespace boost
184 :
185 : #endif
|