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_EX_IO_AWAITABLE_PROMISE_BASE_HPP
11 : #define BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
15 : #include <boost/capy/ex/frame_allocator.hpp>
16 : #include <boost/capy/ex/io_env.hpp>
17 : #include <boost/capy/ex/this_coro.hpp>
18 :
19 : #include <coroutine>
20 : #include <memory_resource>
21 : #include <stop_token>
22 : #include <type_traits>
23 :
24 : namespace boost {
25 : namespace capy {
26 :
27 : /** CRTP mixin that adds I/O awaitable support to a promise type.
28 :
29 : Inherit from this class to enable these capabilities in your coroutine:
30 :
31 : 1. **Frame allocation** — The mixin provides `operator new/delete` that
32 : use the thread-local frame allocator set by `run_async`.
33 :
34 : 2. **Environment storage** — The mixin stores a pointer to the `io_env`
35 : containing the executor, stop token, and allocator for this coroutine.
36 :
37 : 3. **Environment access** — Coroutine code can retrieve the environment
38 : via `co_await this_coro::environment`, or individual fields via
39 : `co_await this_coro::executor`, `co_await this_coro::stop_token`,
40 : and `co_await this_coro::frame_allocator`.
41 :
42 : @tparam Derived The derived promise type (CRTP pattern).
43 :
44 : @par Basic Usage
45 :
46 : For coroutines that need to access their execution environment:
47 :
48 : @code
49 : struct my_task
50 : {
51 : struct promise_type : io_awaitable_promise_base<promise_type>
52 : {
53 : my_task get_return_object();
54 : std::suspend_always initial_suspend() noexcept;
55 : std::suspend_always final_suspend() noexcept;
56 : void return_void();
57 : void unhandled_exception();
58 : };
59 :
60 : // ... awaitable interface ...
61 : };
62 :
63 : my_task example()
64 : {
65 : auto env = co_await this_coro::environment;
66 : // Access env->executor, env->stop_token, env->frame_allocator
67 :
68 : // Or use fine-grained accessors:
69 : auto ex = co_await this_coro::executor;
70 : auto token = co_await this_coro::stop_token;
71 : auto* alloc = co_await this_coro::frame_allocator;
72 : }
73 : @endcode
74 :
75 : @par Custom Awaitable Transformation
76 :
77 : If your promise needs to transform awaitables (e.g., for affinity or
78 : logging), override `transform_awaitable` instead of `await_transform`:
79 :
80 : @code
81 : struct promise_type : io_awaitable_promise_base<promise_type>
82 : {
83 : template<typename A>
84 : auto transform_awaitable(A&& a)
85 : {
86 : // Your custom transformation logic
87 : return std::forward<A>(a);
88 : }
89 : };
90 : @endcode
91 :
92 : The mixin's `await_transform` intercepts @ref this_coro::environment_tag
93 : and the fine-grained tag types (@ref this_coro::executor_tag,
94 : @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
95 : then delegates all other awaitables to your `transform_awaitable`.
96 :
97 : @par Making Your Coroutine an IoAwaitable
98 :
99 : The mixin handles the "inside the coroutine" part—accessing the
100 : environment. To receive the environment when your coroutine is awaited
101 : (satisfying @ref IoAwaitable), implement the `await_suspend` overload
102 : on your coroutine return type:
103 :
104 : @code
105 : struct my_task
106 : {
107 : struct promise_type : io_awaitable_promise_base<promise_type> { ... };
108 :
109 : std::coroutine_handle<promise_type> h_;
110 :
111 : // IoAwaitable await_suspend receives and stores the environment
112 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
113 : {
114 : h_.promise().set_environment(env);
115 : // ... rest of suspend logic ...
116 : }
117 : };
118 : @endcode
119 :
120 : @par Thread Safety
121 : The environment is stored during `await_suspend` and read during
122 : `co_await this_coro::environment`. These occur on the same logical
123 : thread of execution, so no synchronization is required.
124 :
125 : @see this_coro::environment, this_coro::executor,
126 : this_coro::stop_token, this_coro::frame_allocator
127 : @see io_env
128 : @see IoAwaitable
129 : */
130 : template<typename Derived>
131 : class io_awaitable_promise_base
132 : : public frame_alloc_mixin
133 : {
134 : io_env const* env_ = nullptr;
135 : mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
136 :
137 : public:
138 HIT 5053 : ~io_awaitable_promise_base()
139 : {
140 : // Abnormal teardown: destroy orphaned continuation
141 5053 : if(cont_ != std::noop_coroutine())
142 143 : cont_.destroy();
143 5053 : }
144 :
145 : //----------------------------------------------------------
146 : // Continuation support
147 : //----------------------------------------------------------
148 :
149 : /** Store the continuation to resume on completion.
150 :
151 : Call this from your coroutine type's `await_suspend` overload
152 : to set up the completion path. The `final_suspend` awaiter
153 : returns this handle via unconditional symmetric transfer.
154 :
155 : @param cont The continuation to resume on completion.
156 : */
157 4970 : void set_continuation(std::coroutine_handle<> cont) noexcept
158 : {
159 4970 : cont_ = cont;
160 4970 : }
161 :
162 : /** Return and consume the stored continuation handle.
163 :
164 : Resets the stored handle to `noop_coroutine()` so the
165 : destructor will not double-destroy it.
166 :
167 : @return The continuation for symmetric transfer.
168 : */
169 4887 : std::coroutine_handle<> continuation() const noexcept
170 : {
171 4887 : return std::exchange(cont_, std::noop_coroutine());
172 : }
173 :
174 : //----------------------------------------------------------
175 : // Environment support
176 : //----------------------------------------------------------
177 :
178 : /** Store a pointer to the execution environment.
179 :
180 : Call this from your coroutine type's `await_suspend`
181 : overload to make the environment available via
182 : `co_await this_coro::environment`. The pointed-to
183 : `io_env` must outlive this coroutine.
184 :
185 : @param env The environment to store.
186 : */
187 5050 : void set_environment(io_env const* env) noexcept
188 : {
189 5050 : env_ = env;
190 5050 : }
191 :
192 : /** Return the stored execution environment.
193 :
194 : @return The environment.
195 : */
196 16602 : io_env const* environment() const noexcept
197 : {
198 16602 : BOOST_CAPY_ASSERT(env_);
199 16602 : return env_;
200 : }
201 :
202 : /** Transform an awaitable before co_await.
203 :
204 : Override this in your derived promise type to customize how
205 : awaitables are transformed. The default implementation passes
206 : the awaitable through unchanged.
207 :
208 : @param a The awaitable expression from `co_await a`.
209 :
210 : @return The transformed awaitable.
211 : */
212 : template<typename A>
213 : decltype(auto) transform_awaitable(A&& a)
214 : {
215 : return std::forward<A>(a);
216 : }
217 :
218 : /** Intercept co_await expressions.
219 :
220 : This function handles @ref this_coro::environment_tag and
221 : the fine-grained tags (@ref this_coro::executor_tag,
222 : @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
223 : specially, returning an awaiter that yields the stored value.
224 : All other awaitables are delegated to @ref transform_awaitable.
225 :
226 : @param t The awaited expression.
227 :
228 : @return An awaiter for the expression.
229 : */
230 : template<typename T>
231 9219 : auto await_transform(T&& t)
232 : {
233 : using Tag = std::decay_t<T>;
234 :
235 : if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
236 : {
237 18 : BOOST_CAPY_ASSERT(env_);
238 : struct awaiter
239 : {
240 : io_env const* env_;
241 16 : bool await_ready() const noexcept { return true; }
242 2 : void await_suspend(std::coroutine_handle<>) const noexcept { }
243 15 : io_env const* await_resume() const noexcept { return env_; }
244 : };
245 18 : return awaiter{env_};
246 : }
247 : else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
248 : {
249 3 : BOOST_CAPY_ASSERT(env_);
250 : struct awaiter
251 : {
252 : executor_ref executor_;
253 2 : bool await_ready() const noexcept { return true; }
254 : void await_suspend(std::coroutine_handle<>) const noexcept { }
255 2 : executor_ref await_resume() const noexcept { return executor_; }
256 : };
257 3 : return awaiter{env_->executor};
258 : }
259 : else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
260 : {
261 15 : BOOST_CAPY_ASSERT(env_);
262 : struct awaiter
263 : {
264 : std::stop_token token_;
265 14 : bool await_ready() const noexcept { return true; }
266 MIS 0 : void await_suspend(std::coroutine_handle<>) const noexcept { }
267 HIT 14 : std::stop_token await_resume() const noexcept { return token_; }
268 : };
269 15 : return awaiter{env_->stop_token};
270 : }
271 : else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
272 : {
273 8 : BOOST_CAPY_ASSERT(env_);
274 : struct awaiter
275 : {
276 : std::pmr::memory_resource* frame_allocator_;
277 6 : bool await_ready() const noexcept { return true; }
278 MIS 0 : void await_suspend(std::coroutine_handle<>) const noexcept { }
279 HIT 7 : std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
280 : };
281 8 : return awaiter{env_->frame_allocator};
282 : }
283 : else
284 : {
285 7000 : return static_cast<Derived*>(this)->transform_awaitable(
286 9175 : std::forward<T>(t));
287 : }
288 : }
289 : };
290 :
291 : } // namespace capy
292 : } // namespace boost
293 :
294 : #endif
|