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_CONTEXT_AWAITABLE_PROMISE_BASE_HPP
12 : #define BOOST_CAPY_EX_CONTEXT_AWAITABLE_PROMISE_BASE_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/ex/abandon.hpp>
16 : #include <boost/capy/ex/await_context.hpp>
17 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
18 : #include <boost/capy/ex/frame_allocator.hpp>
19 : #include <boost/capy/ex/this_coro.hpp>
20 :
21 : #include <coroutine>
22 : #include <memory_resource>
23 : #include <stop_token>
24 : #include <type_traits>
25 :
26 : namespace boost {
27 : namespace capy {
28 :
29 : /** CRTP mixin that adds I/O awaitable support to a promise type.
30 :
31 : Inherit from this class to enable these capabilities in your coroutine:
32 :
33 : 1. **Frame allocation** — The mixin provides `operator new/delete` that
34 : use the thread-local frame allocator set by `run_async`.
35 :
36 : 2. **Environment storage** — The mixin stores a pointer to the `await_context`
37 : containing the executor, stop token, and allocator for this coroutine.
38 :
39 : 3. **Environment access** — Coroutine code can retrieve the environment
40 : via `co_await this_coro::environment`, or individual fields via
41 : `co_await this_coro::executor`, `co_await this_coro::stop_token`,
42 : and `co_await this_coro::frame_allocator`.
43 :
44 : @tparam Derived The derived promise type (CRTP pattern).
45 :
46 : @par Basic Usage
47 :
48 : For coroutines that need to access their execution environment:
49 :
50 : @par !example example_1
51 :
52 :
53 : @par Custom Awaitable Transformation
54 :
55 : If your promise needs to transform awaitables (e.g., for affinity or
56 : logging), override `transform_awaitable` instead of `await_transform`:
57 :
58 : @par !example example_2
59 :
60 :
61 : The mixin's `await_transform` intercepts @ref this_coro::environment_tag
62 : and the fine-grained tag types (@ref this_coro::executor_tag,
63 : @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
64 : then delegates all other awaitables to your `transform_awaitable`.
65 :
66 : @par Making Your Coroutine a ContextAwaitable
67 :
68 : The mixin handles the "inside the coroutine" part—accessing the
69 : environment. To receive the environment when your coroutine is awaited
70 : (satisfying @ref ContextAwaitable), implement the `await_suspend` overload
71 : on your coroutine return type:
72 :
73 : @par !example example_3
74 :
75 :
76 : @par Ownership
77 : A coroutine's frame is owned by the object that awaits it, such as
78 : the parent's `task`, so a chain is destroyed from its top down, each
79 : frame freeing the one it awaits. The suspended coroutine at the
80 : bottom is held by whoever will resume it: an operation, queue or
81 : primitive. A holder that will never resume it calls @ref abandon,
82 : which unwinds the chain's segment from its owner (see
83 : @ref chain_owner) rather than destroying the bottom frame.
84 : Destroying a frame that is still awaited, other than in that
85 : unwind, calls `std::terminate`.
86 :
87 : @par Thread Safety
88 : The environment is stored during `await_suspend` and read during
89 : `co_await this_coro::environment`. These occur on the same logical
90 : thread of execution, so no synchronization is required.
91 :
92 : @note A chain waiting on a primitive stored in its own frames owns
93 : itself through that primitive; it is freed only by a stop request or
94 : by the primitive being signalled. A mutex must outlive every
95 : coroutine holding or handed its lock. A cross-thread `wake()` racing
96 : the teardown of an armed waker's frame on its executor thread is a
97 : precondition violation.
98 :
99 : @see this_coro::environment, this_coro::executor,
100 : this_coro::stop_token, this_coro::frame_allocator
101 : @see await_context
102 : @see ContextAwaitable
103 : */
104 : template<typename Derived>
105 : class context_awaitable_promise_base
106 : : public frame_alloc_mixin
107 : {
108 : await_context const* env_ = nullptr;
109 : mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
110 :
111 : public:
112 : //----------------------------------------------------------
113 : // Continuation support
114 : //----------------------------------------------------------
115 :
116 : /** Store the continuation to resume on completion.
117 :
118 : Call this from your coroutine type's `await_suspend` overload
119 : to set up the completion path. The `final_suspend` awaiter
120 : returns this handle via unconditional symmetric transfer.
121 :
122 : @param cont The continuation to resume on completion.
123 : */
124 HIT 107627 : void set_continuation(std::coroutine_handle<> cont) noexcept
125 : {
126 107627 : cont_ = cont;
127 107627 : }
128 :
129 : /** Return and consume the stored continuation handle.
130 :
131 : Resets the stored handle to `noop_coroutine()` so the
132 : destructor does not double-destroy it.
133 :
134 : @return The continuation for symmetric transfer.
135 : */
136 5062 : std::coroutine_handle<> continuation() const noexcept
137 : {
138 5062 : return std::exchange(cont_, std::noop_coroutine());
139 : }
140 :
141 : //----------------------------------------------------------
142 : // Environment support
143 : //----------------------------------------------------------
144 :
145 : /** Store a pointer to the execution environment.
146 :
147 : Call this from your coroutine type's `await_suspend`
148 : overload to make the environment available via
149 : `co_await this_coro::environment`. The pointed-to
150 : `await_context` must outlive this coroutine.
151 :
152 : @param env The environment to store.
153 : */
154 107713 : void set_environment(await_context const* env) noexcept
155 : {
156 107713 : env_ = env;
157 107713 : }
158 :
159 : /** Return the stored execution environment.
160 :
161 : @return The environment.
162 : */
163 221255 : await_context const* environment() const noexcept
164 : {
165 221255 : BOOST_CAPY_ASSERT(env_);
166 221255 : return env_;
167 : }
168 :
169 : /** Transform an awaitable before co_await.
170 :
171 : Override this in your derived promise type to customize how
172 : awaitables are transformed. The default implementation passes
173 : the awaitable through unchanged.
174 :
175 : @param a The awaitable expression from `co_await a`.
176 :
177 : @return The transformed awaitable.
178 : */
179 : template<typename A>
180 1 : decltype(auto) transform_awaitable(A&& a)
181 : {
182 1 : return std::forward<A>(a);
183 : }
184 :
185 : /** Intercept co_await expressions.
186 :
187 : This function handles @ref this_coro::environment_tag and
188 : the fine-grained tags (@ref this_coro::executor_tag,
189 : @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
190 : specially, returning an awaiter that yields the stored value.
191 : All other awaitables are delegated to @ref transform_awaitable.
192 :
193 : @param t The awaited expression.
194 :
195 : @return An awaiter for the expression.
196 : */
197 : template<typename T>
198 107809 : auto await_transform(T&& t)
199 : {
200 : using Tag = std::decay_t<T>;
201 :
202 : if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
203 : {
204 24 : BOOST_CAPY_ASSERT(env_);
205 : struct awaiter
206 : {
207 : await_context const* env_;
208 22 : bool await_ready() const noexcept { return true; }
209 2 : void await_suspend(std::coroutine_handle<>) const noexcept { }
210 21 : await_context const* await_resume() const noexcept { return env_; }
211 : };
212 24 : return awaiter{env_};
213 : }
214 : else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
215 : {
216 4 : BOOST_CAPY_ASSERT(env_);
217 : struct awaiter
218 : {
219 : executor_ref executor_;
220 3 : bool await_ready() const noexcept { return true; }
221 : void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
222 3 : executor_ref await_resume() const noexcept { return executor_; }
223 : };
224 4 : return awaiter{env_->executor};
225 : }
226 : else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
227 : {
228 24 : BOOST_CAPY_ASSERT(env_);
229 : struct awaiter
230 : {
231 : std::stop_token token_;
232 23 : bool await_ready() const noexcept { return true; }
233 : void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
234 23 : std::stop_token await_resume() const noexcept { return token_; }
235 : };
236 24 : return awaiter{env_->stop_token};
237 : }
238 : else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
239 : {
240 8 : BOOST_CAPY_ASSERT(env_);
241 : struct awaiter
242 : {
243 : std::pmr::memory_resource* frame_allocator_;
244 6 : bool await_ready() const noexcept { return true; }
245 : void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
246 7 : std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
247 : };
248 8 : return awaiter{env_->frame_allocator};
249 : }
250 : else
251 : {
252 2506 : return static_cast<Derived*>(this)->transform_awaitable(
253 107749 : std::forward<T>(t));
254 : }
255 : }
256 : };
257 :
258 : } // namespace capy
259 : } // namespace boost
260 :
261 : #endif
|