Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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_STOP_TOKEN_SUPPORT_HPP
11 : #define BOOST_CAPY_EX_STOP_TOKEN_SUPPORT_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/ex/any_coro.hpp>
15 : #include <boost/capy/ex/get_stop_token.hpp>
16 :
17 : #include <coroutine>
18 : #include <stop_token>
19 : #include <type_traits>
20 :
21 : namespace boost {
22 : namespace capy {
23 :
24 : /** CRTP mixin that adds stop token support to a promise type.
25 :
26 : Inherit from this class to enable two capabilities in your coroutine:
27 :
28 : 1. **Stop token storage** — The mixin stores the `std::stop_token`
29 : that was passed when your coroutine was awaited.
30 :
31 : 2. **Stop token access** — Coroutine code can retrieve the token via
32 : `co_await get_stop_token()`.
33 :
34 : @tparam Derived The derived promise type (CRTP pattern).
35 :
36 : @par Basic Usage
37 :
38 : For coroutines that only need to access their stop token:
39 :
40 : @code
41 : struct my_task
42 : {
43 : struct promise_type : stop_token_support<promise_type>
44 : {
45 : my_task get_return_object();
46 : std::suspend_always initial_suspend() noexcept;
47 : std::suspend_always final_suspend() noexcept;
48 : void return_void();
49 : void unhandled_exception();
50 : };
51 :
52 : // ... awaitable interface ...
53 : };
54 :
55 : my_task example()
56 : {
57 : auto token = co_await get_stop_token();
58 : // Use token...
59 : }
60 : @endcode
61 :
62 : @par Custom Awaitable Transformation
63 :
64 : If your promise needs to transform awaitables (e.g., for affinity or
65 : logging), override `transform_awaitable` instead of `await_transform`:
66 :
67 : @code
68 : struct promise_type : stop_token_support<promise_type>
69 : {
70 : any_executor_ref ex_;
71 :
72 : template<typename A>
73 : auto transform_awaitable(A&& a)
74 : {
75 : // Your custom transformation logic
76 : return make_affine(std::forward<A>(a), ex_);
77 : }
78 : };
79 : @endcode
80 :
81 : The mixin's `await_transform` intercepts @ref get_stop_token_tag and
82 : delegates all other awaitables to your `transform_awaitable`.
83 :
84 : @par Making Your Coroutine Stoppable
85 :
86 : The mixin handles the "inside the coroutine" part—accessing the token.
87 : To receive a token when your coroutine is awaited (satisfying
88 : @ref IoAwaitable), implement the stoppable `await_suspend`
89 : overload on your coroutine return type:
90 :
91 : @code
92 : struct my_task
93 : {
94 : struct promise_type : stop_token_support<promise_type> { ... };
95 :
96 : std::coroutine_handle<promise_type> h_;
97 :
98 : // Stoppable await_suspend receives and stores the token
99 : template<class Ex>
100 : any_coro await_suspend(any_coro cont, Ex const& ex, std::stop_token token)
101 : {
102 : h_.promise().set_stop_token(token); // Store via mixin API
103 : // ... rest of suspend logic ...
104 : }
105 : };
106 : @endcode
107 :
108 : @par Thread Safety
109 : The stop token is stored during `await_suspend` and read during
110 : `co_await get_stop_token()`. These occur on the same logical thread
111 : of execution, so no synchronization is required.
112 :
113 : @see get_stop_token
114 : @see get_stop_token_tag
115 : @see IoAwaitable
116 : */
117 : template<typename Derived>
118 : class stop_token_support
119 : {
120 : std::stop_token stop_token_;
121 :
122 : public:
123 : /** Store a stop token for later retrieval.
124 :
125 : Call this from your coroutine type's stoppable `await_suspend`
126 : overload to make the token available via `co_await get_stop_token()`.
127 :
128 : @param token The stop token to store.
129 : */
130 161 : void set_stop_token(std::stop_token token) noexcept
131 : {
132 161 : stop_token_ = token;
133 161 : }
134 :
135 : /** Return the stored stop token.
136 :
137 : @return The stop token, or a default-constructed token if none was set.
138 : */
139 66 : std::stop_token const& stop_token() const noexcept
140 : {
141 66 : return stop_token_;
142 : }
143 :
144 : /** Transform an awaitable before co_await.
145 :
146 : Override this in your derived promise type to customize how
147 : awaitables are transformed. The default implementation passes
148 : the awaitable through unchanged.
149 :
150 : @param a The awaitable expression from `co_await a`.
151 :
152 : @return The transformed awaitable.
153 : */
154 : template<typename A>
155 : decltype(auto) transform_awaitable(A&& a)
156 : {
157 : return std::forward<A>(a);
158 : }
159 :
160 : /** Intercept co_await expressions.
161 :
162 : This function handles @ref get_stop_token_tag specially, returning
163 : an awaiter that yields the stored stop token. All other awaitables
164 : are delegated to @ref transform_awaitable.
165 :
166 : @param t The awaited expression.
167 :
168 : @return An awaiter for the expression.
169 : */
170 : template<typename T>
171 78 : auto await_transform(T&& t)
172 : {
173 : if constexpr (std::is_same_v<std::decay_t<T>, get_stop_token_tag>)
174 : {
175 : struct awaiter
176 : {
177 : std::stop_token token_;
178 :
179 12 : bool await_ready() const noexcept
180 : {
181 12 : return true;
182 : }
183 :
184 1 : void await_suspend(any_coro) const noexcept
185 : {
186 1 : }
187 :
188 11 : std::stop_token await_resume() const noexcept
189 : {
190 11 : return token_;
191 : }
192 : };
193 13 : return awaiter{stop_token_};
194 : }
195 : else
196 : {
197 25 : return static_cast<Derived*>(this)->transform_awaitable(
198 65 : std::forward<T>(t));
199 : }
200 : }
201 : };
202 :
203 : } // namespace capy
204 : } // namespace boost
205 :
206 : #endif
|