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_STRAND_HPP
12 : #define BOOST_CAPY_EX_STRAND_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/continuation.hpp>
16 : #include <coroutine>
17 : #include <boost/capy/ex/detail/strand_service.hpp>
18 :
19 : #include <type_traits>
20 :
21 : namespace boost {
22 : namespace capy {
23 :
24 : /** Provides serialized coroutine execution for any executor type.
25 :
26 : A strand wraps an inner executor and ensures that coroutines
27 : dispatched through it never run concurrently. At most one
28 : coroutine executes at a time within a strand, even when the
29 : underlying executor runs on multiple threads.
30 :
31 : Strands are lightweight handles that can be copied freely.
32 : Copies share the same internal serialization state, so
33 : coroutines dispatched through any copy are serialized with
34 : respect to all other copies.
35 :
36 : @par Invariant
37 : Coroutines resumed through a strand shall not run concurrently.
38 :
39 : @par Implementation
40 : Each strand allocates a private serialization state. Strands
41 : constructed from the same execution context share a small pool
42 : of mutexes (193 entries) selected by hash. Mutex sharing causes
43 : only brief contention on the push/pop critical section, never
44 : cross-strand state sharing. Construction cost: one
45 : `std::make_shared` per strand.
46 :
47 : @par Executor Concept
48 : This class satisfies the `Executor` concept, providing:
49 : - `context()` - Returns the underlying execution context
50 : - `on_work_started()` / `on_work_finished()` - Work tracking
51 : - `dispatch(continuation&)` - May run immediately if already executing in this strand
52 : - `post(continuation&)` - Always queues for later execution
53 :
54 : @par Preconditions
55 : A strand holds only a non-owning reference to its inner executor's
56 : execution context (for example a `thread_pool`). That context must
57 : outlive every post() and dispatch() call; posting or dispatching
58 : concurrently with, or after, the context's destruction is undefined
59 : behavior. To guarantee this, submit work through @ref run_async or
60 : @ref run. Their operations are work-tracked, so the context's
61 : `join()` waits for them. Call `join()` on the context before
62 : destroying it, rather than posting to a strand from an external
63 : thread the context does not track. Destroying the strand handle
64 : itself is always safe, including after the context is
65 : destroyed.
66 :
67 : Coroutines queued on the strand when its context shuts down are
68 : passed to @ref abandon rather than resumed.
69 :
70 : @par Thread Safety
71 : Distinct objects: Safe.
72 : Shared objects: Safe.
73 :
74 : @par Example
75 : @par !example example
76 :
77 :
78 : @tparam Ex The type of the underlying executor. Must
79 : satisfy the `Executor` concept.
80 :
81 : @see Executor
82 : */
83 : template<typename Ex>
84 : class strand
85 : {
86 : std::shared_ptr<detail::strand_impl> impl_;
87 : Ex ex_;
88 :
89 : friend struct strand_test;
90 :
91 : public:
92 : /** Names the executor type this `strand<Ex>` wraps.
93 : */
94 : using inner_executor_type = Ex;
95 :
96 : /** Construct a strand for the specified executor.
97 :
98 : Allocates a fresh strand implementation from the service
99 : associated with the executor's context.
100 :
101 : @param ex The inner executor to wrap. Coroutines are
102 : ultimately dispatched through this executor.
103 :
104 : @note This constructor is disabled if the argument is a
105 : strand type, to prevent strand-of-strand wrapping.
106 : */
107 : template<typename Ex1,
108 : typename = std::enable_if_t<
109 : !std::is_same_v<std::decay_t<Ex1>, strand> &&
110 : !detail::is_strand<std::decay_t<Ex1>>::value &&
111 : std::is_convertible_v<Ex1, Ex>>>
112 : explicit
113 HIT 12467 : strand(Ex1&& ex)
114 12467 : : impl_(detail::get_strand_service(ex.context())
115 12467 : .create_implementation())
116 12467 : , ex_(std::forward<Ex1>(ex))
117 : {
118 12467 : }
119 :
120 : /** Construct a copy.
121 :
122 : Creates a strand that shares serialization state with
123 : the original. Coroutines dispatched through either strand
124 : are serialized with respect to each other.
125 :
126 : @param other The strand to copy.
127 : */
128 100015 : strand(strand const& other) = default;
129 :
130 : /** Construct by moving.
131 :
132 : @param other The strand to move from.
133 :
134 : @note A moved-from strand is only safe to destroy
135 : or reassign.
136 : */
137 511479 : strand(strand&& other) = default;
138 :
139 : /** Assign by copying.
140 :
141 : Shares serialization state with `other`, as the copy
142 : constructor does.
143 :
144 : @param other The strand to copy.
145 :
146 : @return A reference to `*this`.
147 : */
148 1 : strand& operator=(strand const& other) = default;
149 :
150 : /** Assign by moving.
151 :
152 : @param other The strand to move from.
153 :
154 : @return A reference to `*this`.
155 :
156 : @note A moved-from strand is only safe to destroy
157 : or reassign.
158 : */
159 1 : strand& operator=(strand&& other) = default;
160 :
161 : /** Return the underlying executor.
162 :
163 : @return A const reference to the inner executor.
164 : */
165 : Ex const&
166 1 : get_inner_executor() const noexcept
167 : {
168 1 : return ex_;
169 : }
170 :
171 : /** Return the underlying execution context.
172 :
173 : @return A reference to the execution context associated
174 : with the inner executor.
175 : */
176 : auto&
177 100006 : context() const noexcept
178 : {
179 100006 : return ex_.context();
180 : }
181 :
182 : /** Notify that work has started.
183 :
184 : Delegates to the inner executor's `on_work_started()`. For a
185 : `thread_pool` inner executor, this increments the count that
186 : `join()` blocks on.
187 : */
188 : void
189 100012 : on_work_started() const noexcept
190 : {
191 100012 : ex_.on_work_started();
192 100012 : }
193 :
194 : /** Notify that work has finished.
195 :
196 : Delegates to the inner executor's `on_work_finished()`. For a
197 : `thread_pool` inner executor, this decrements the count that
198 : `join()` blocks on.
199 : */
200 : void
201 100012 : on_work_finished() const noexcept
202 : {
203 100012 : ex_.on_work_finished();
204 100012 : }
205 :
206 : /** Determine whether the strand is running in the current thread.
207 :
208 : @return true if the current thread is executing a coroutine
209 : within this strand's dispatch loop.
210 : */
211 : bool
212 4 : running_in_this_thread() const noexcept
213 : {
214 4 : return detail::strand_service::running_in_this_thread(*impl_);
215 : }
216 :
217 : /** Compare two strands for equality.
218 :
219 : Two strands are equal if they share the same internal
220 : serialization state. Equal strands serialize coroutines
221 : with respect to each other.
222 :
223 : @param other The strand to compare against.
224 : @return true if both strands share the same implementation.
225 : */
226 : bool
227 499505 : operator==(strand const& other) const noexcept
228 : {
229 499505 : return impl_.get() == other.impl_.get();
230 : }
231 :
232 : /** Post a continuation to the strand.
233 :
234 : The continuation is always queued for execution, never resumed
235 : immediately. When the strand becomes available, queued
236 : work executes in FIFO order on the underlying executor.
237 :
238 : @par Ordering
239 : Guarantees strict FIFO ordering relative to other post() calls.
240 : Use this instead of dispatch() when ordering matters.
241 :
242 : @param c The continuation to post. The caller retains
243 : ownership; the continuation must remain valid until
244 : it is dequeued and resumed.
245 :
246 : @par Preconditions
247 : The strand's execution context must outlive this call. Posting
248 : concurrently with, or after, that context's destruction is
249 : undefined behavior.
250 : */
251 : void
252 231429 : post(continuation& c) const
253 : {
254 231429 : detail::strand_service::post(impl_, executor_ref(ex_), c);
255 231425 : }
256 :
257 : /** Dispatch a continuation through the strand.
258 :
259 : Returns a handle for symmetric transfer. If the calling
260 : thread is already executing within this strand, returns `c.h`.
261 : Otherwise, the continuation is queued and
262 : `std::noop_coroutine()` is returned.
263 :
264 : @par Ordering
265 : Callers requiring strict FIFO ordering should use post()
266 : instead, which always queues the continuation.
267 :
268 : @param c The continuation to dispatch. The caller retains
269 : ownership; the continuation must remain valid until
270 : it is dequeued and resumed.
271 :
272 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
273 :
274 : @par Preconditions
275 : The strand's execution context must outlive this call.
276 : Dispatching concurrently with, or after, that context's
277 : destruction is undefined behavior.
278 : */
279 : std::coroutine_handle<>
280 100013 : dispatch(continuation& c) const
281 : {
282 100013 : return detail::strand_service::dispatch(impl_, executor_ref(ex_), c);
283 : }
284 : };
285 :
286 : /** Deduce the executor type from the constructor argument.
287 :
288 : @tparam Ex The wrapped executor type.
289 : */
290 : template<typename Ex>
291 : strand(Ex) -> strand<Ex>;
292 :
293 : } // namespace capy
294 : } // namespace boost
295 :
296 : #endif
|