TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/capy
9 : //
10 :
11 : #ifndef BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
12 : #define BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
16 : #include <boost/capy/ex/frame_allocator.hpp>
17 : #include <boost/capy/ex/io_env.hpp>
18 : #include <boost/capy/ex/this_coro.hpp>
19 :
20 : #include <coroutine>
21 : #include <memory_resource>
22 : #include <stop_token>
23 : #include <type_traits>
24 :
25 : namespace boost {
26 : namespace capy {
27 :
28 : /** CRTP mixin that adds I/O awaitable support to a promise type.
29 :
30 : Inherit from this class to enable these capabilities in your coroutine:
31 :
32 : 1. **Frame allocation** — The mixin provides `operator new/delete` that
33 : use the thread-local frame allocator set by `run_async`.
34 :
35 : 2. **Environment storage** — The mixin stores a pointer to the `io_env`
36 : containing the executor, stop token, and allocator for this coroutine.
37 :
38 : 3. **Environment access** — Coroutine code can retrieve the environment
39 : via `co_await this_coro::environment`, or individual fields via
40 : `co_await this_coro::executor`, `co_await this_coro::stop_token`,
41 : and `co_await this_coro::frame_allocator`.
42 :
43 : @tparam Derived The derived promise type (CRTP pattern).
44 :
45 : @par Basic Usage
46 :
47 : For coroutines that need to access their execution environment:
48 :
49 : @par !example example_1
50 :
51 :
52 : @par Custom Awaitable Transformation
53 :
54 : If your promise needs to transform awaitables (e.g., for affinity or
55 : logging), override `transform_awaitable` instead of `await_transform`:
56 :
57 : @par !example example_2
58 :
59 :
60 : The mixin's `await_transform` intercepts @ref this_coro::environment_tag
61 : and the fine-grained tag types (@ref this_coro::executor_tag,
62 : @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
63 : then delegates all other awaitables to your `transform_awaitable`.
64 :
65 : @par Making Your Coroutine an IoAwaitable
66 :
67 : The mixin handles the "inside the coroutine" part—accessing the
68 : environment. To receive the environment when your coroutine is awaited
69 : (satisfying @ref IoAwaitable), implement the `await_suspend` overload
70 : on your coroutine return type:
71 :
72 : @par !example example_3
73 :
74 :
75 : @par Thread Safety
76 : The environment is stored during `await_suspend` and read during
77 : `co_await this_coro::environment`. These occur on the same logical
78 : thread of execution, so no synchronization is required.
79 :
80 : @see this_coro::environment, this_coro::executor,
81 : this_coro::stop_token, this_coro::frame_allocator
82 : @see io_env
83 : @see IoAwaitable
84 : */
85 : template<typename Derived>
86 : class io_awaitable_promise_base
87 : : public frame_alloc_mixin
88 : {
89 : io_env const* env_ = nullptr;
90 : mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
91 :
92 : public:
93 : /** Destroy the promise, destroying an orphaned continuation.
94 :
95 : A continuation is still stored only when the coroutine never
96 : reached `final_suspend`, because @ref continuation consumes the
97 : stored handle. Destroying it here is what keeps an abandoned
98 : coroutine from leaking the trampoline frame that was waiting on it.
99 :
100 : @par Preconditions
101 : No parent coroutine is awaiting this one. A parent's `await_suspend`
102 : installs its own handle as the continuation, so destroying such a
103 : coroutine directly would destroy the parent from here as well. See
104 : @ref task::handle and @ref quitter::handle for the contract.
105 : */
106 HIT 2819 : ~io_awaitable_promise_base()
107 : {
108 : // Abnormal teardown: destroy an orphaned continuation, e.g.
109 : // a run_async trampoline when the task is destroyed before
110 : // reaching final_suspend. Callers must not destroy a task
111 : // via handle().destroy() while it is being awaited by a
112 : // parent coroutine: that puts cont_ under another owner
113 : // and would produce a double-destroy from this branch. See
114 : // task::handle() / quitter::handle() for the contract.
115 2819 : if(cont_ != std::noop_coroutine())
116 127 : cont_.destroy();
117 2819 : }
118 :
119 : //----------------------------------------------------------
120 : // Continuation support
121 : //----------------------------------------------------------
122 :
123 : /** Store the continuation to resume on completion.
124 :
125 : Call this from your coroutine type's `await_suspend` overload
126 : to set up the completion path. The `final_suspend` awaiter
127 : returns this handle via unconditional symmetric transfer.
128 :
129 : @param cont The continuation to resume on completion.
130 : */
131 2730 : void set_continuation(std::coroutine_handle<> cont) noexcept
132 : {
133 2730 : cont_ = cont;
134 2730 : }
135 :
136 : /** Return and consume the stored continuation handle.
137 :
138 : Resets the stored handle to `noop_coroutine()` so the
139 : destructor does not double-destroy it.
140 :
141 : @return The continuation for symmetric transfer.
142 : */
143 2667 : std::coroutine_handle<> continuation() const noexcept
144 : {
145 2667 : return std::exchange(cont_, std::noop_coroutine());
146 : }
147 :
148 : //----------------------------------------------------------
149 : // Environment support
150 : //----------------------------------------------------------
151 :
152 : /** Store a pointer to the execution environment.
153 :
154 : Call this from your coroutine type's `await_suspend`
155 : overload to make the environment available via
156 : `co_await this_coro::environment`. The pointed-to
157 : `io_env` must outlive this coroutine.
158 :
159 : @param env The environment to store.
160 : */
161 2815 : void set_environment(io_env const* env) noexcept
162 : {
163 2815 : env_ = env;
164 2815 : }
165 :
166 : /** Return the stored execution environment.
167 :
168 : @return The environment.
169 : */
170 7891 : io_env const* environment() const noexcept
171 : {
172 7891 : BOOST_CAPY_ASSERT(env_);
173 7891 : return env_;
174 : }
175 :
176 : /** Transform an awaitable before co_await.
177 :
178 : Override this in your derived promise type to customize how
179 : awaitables are transformed. The default implementation passes
180 : the awaitable through unchanged.
181 :
182 : @param a The awaitable expression from `co_await a`.
183 :
184 : @return The transformed awaitable.
185 : */
186 : template<typename A>
187 : decltype(auto) transform_awaitable(A&& a)
188 : {
189 : return std::forward<A>(a);
190 : }
191 :
192 : /** Intercept co_await expressions.
193 :
194 : This function handles @ref this_coro::environment_tag and
195 : the fine-grained tags (@ref this_coro::executor_tag,
196 : @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
197 : specially, returning an awaiter that yields the stored value.
198 : All other awaitables are delegated to @ref transform_awaitable.
199 :
200 : @param t The awaited expression.
201 :
202 : @return An awaiter for the expression.
203 : */
204 : template<typename T>
205 2953 : auto await_transform(T&& t)
206 : {
207 : using Tag = std::decay_t<T>;
208 :
209 : if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
210 : {
211 18 : BOOST_CAPY_ASSERT(env_);
212 : struct awaiter
213 : {
214 : io_env const* env_;
215 16 : bool await_ready() const noexcept { return true; }
216 2 : void await_suspend(std::coroutine_handle<>) const noexcept { }
217 15 : io_env const* await_resume() const noexcept { return env_; }
218 : };
219 18 : return awaiter{env_};
220 : }
221 : else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
222 : {
223 4 : BOOST_CAPY_ASSERT(env_);
224 : struct awaiter
225 : {
226 : executor_ref executor_;
227 3 : bool await_ready() const noexcept { return true; }
228 : void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
229 3 : executor_ref await_resume() const noexcept { return executor_; }
230 : };
231 4 : return awaiter{env_->executor};
232 : }
233 : else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
234 : {
235 24 : BOOST_CAPY_ASSERT(env_);
236 : struct awaiter
237 : {
238 : std::stop_token token_;
239 23 : bool await_ready() const noexcept { return true; }
240 : void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
241 23 : std::stop_token await_resume() const noexcept { return token_; }
242 : };
243 24 : return awaiter{env_->stop_token};
244 : }
245 : else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
246 : {
247 8 : BOOST_CAPY_ASSERT(env_);
248 : struct awaiter
249 : {
250 : std::pmr::memory_resource* frame_allocator_;
251 6 : bool await_ready() const noexcept { return true; }
252 : void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
253 7 : std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
254 : };
255 8 : return awaiter{env_->frame_allocator};
256 : }
257 : else
258 : {
259 1340 : return static_cast<Derived*>(this)->transform_awaitable(
260 2899 : std::forward<T>(t));
261 : }
262 : }
263 : };
264 :
265 : } // namespace capy
266 : } // namespace boost
267 :
268 : #endif
|