GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/strand.hpp
Date: 2026-01-17 11:19:32
Exec Total Coverage
Lines: 29 29 100.0%
Functions: 12 12 100.0%
Branches: 2 2 100.0%

Line Branch Exec Source
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_STRAND_HPP
11 #define BOOST_CAPY_EX_STRAND_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/ex/any_coro.hpp>
15 #include <boost/capy/ex/detail/strand_service.hpp>
16
17 #include <type_traits>
18
19 namespace boost {
20 namespace capy {
21
22 //----------------------------------------------------------
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 The strand uses a service-based architecture with a fixed pool
41 of 211 implementation objects. New strands hash to select an
42 impl from the pool. Strands that hash to the same index share
43 serialization, which is harmless (just extra serialization)
44 and rare with 211 buckets.
45
46 @par Executor Concept
47 This class satisfies the `executor` concept, providing:
48 - `context()` - Returns the underlying execution context
49 - `on_work_started()` / `on_work_finished()` - Work tracking
50 - `dispatch(h)` - May run immediately if strand is idle
51 - `post(h)` - Always queues for later execution
52 - `defer(h)` - Same as post (continuation hint)
53
54 @par Thread Safety
55 Distinct objects: Safe.
56 Shared objects: Safe.
57
58 @par Example
59 @code
60 thread_pool pool(4);
61 auto strand = make_strand(pool.get_executor());
62
63 // These coroutines will never run concurrently
64 strand.post(coro1);
65 strand.post(coro2);
66 strand.post(coro3);
67 @endcode
68
69 @tparam Executor The type of the underlying executor. Must
70 satisfy the `executor` concept.
71
72 @see make_strand, executor
73 */
74 template<typename Executor>
75 class strand
76 {
77 detail::strand_impl* impl_;
78 post_dispatcher<Executor> post_;
79
80 public:
81 /** The type of the underlying executor.
82 */
83 using inner_executor_type = Executor;
84
85 /** Construct a strand for the specified executor.
86
87 Obtains a strand implementation from the service associated
88 with the executor's context. The implementation is selected
89 from a fixed pool using a hash function.
90
91 @param ex The inner executor to wrap. Coroutines will
92 ultimately be dispatched through this executor.
93
94 @note This constructor is disabled if the argument is a
95 strand type, to prevent strand-of-strand wrapping.
96 */
97 template<typename Executor1,
98 typename = std::enable_if_t<
99 !std::is_same_v<std::decay_t<Executor1>, strand> &&
100 !detail::is_strand<std::decay_t<Executor1>>::value &&
101 std::is_convertible_v<Executor1, Executor>>>
102 explicit
103 48 strand(Executor1&& ex)
104 48 : impl_(detail::get_strand_service(ex.context())
105 48 .get_implementation())
106 48 , post_(std::forward<Executor1>(ex))
107 {
108 48 }
109
110 /** Copy constructor.
111
112 Creates a strand that shares serialization state with
113 the original. Coroutines dispatched through either strand
114 will be serialized with respect to each other.
115 */
116 strand(strand const&) = default;
117
118 /** Move constructor.
119 */
120 strand(strand&&) = default;
121
122 /** Copy assignment operator.
123 */
124 strand& operator=(strand const&) = default;
125
126 /** Move assignment operator.
127 */
128 strand& operator=(strand&&) = default;
129
130 /** Return the underlying executor.
131
132 @return A const reference to the inner executor.
133 */
134 Executor const&
135 1 get_inner_executor() const noexcept
136 {
137 1 return post_.get_inner_executor();
138 }
139
140 /** Return the underlying execution context.
141
142 @return A reference to the execution context associated
143 with the inner executor.
144 */
145 auto&
146 1 context() const noexcept
147 {
148 1 return post_.get_inner_executor().context();
149 }
150
151 /** Notify that work has started.
152
153 Delegates to the inner executor's `on_work_started()`.
154 This is a no-op for most executor types.
155 */
156 void
157 2 on_work_started() const noexcept
158 {
159 2 post_.get_inner_executor().on_work_started();
160 2 }
161
162 /** Notify that work has finished.
163
164 Delegates to the inner executor's `on_work_finished()`.
165 This is a no-op for most executor types.
166 */
167 void
168 2 on_work_finished() const noexcept
169 {
170 2 post_.get_inner_executor().on_work_finished();
171 2 }
172
173 /** Determine whether the strand is running in the current thread.
174
175 @return true if the current thread is executing a coroutine
176 within this strand's dispatch loop.
177 */
178 bool
179 1 running_in_this_thread() const noexcept
180 {
181 1 return detail::strand_service::running_in_this_thread(*impl_);
182 }
183
184 /** Compare two strands for equality.
185
186 Two strands are equal if they share the same internal
187 serialization state. Equal strands serialize coroutines
188 with respect to each other.
189
190 @param other The strand to compare against.
191 @return true if both strands share the same implementation.
192 */
193 bool
194 3 operator==(strand const& other) const noexcept
195 {
196 3 return impl_ == other.impl_;
197 }
198
199 /** Dispatch a coroutine through the strand.
200
201 If the calling thread is already executing within this strand,
202 the coroutine is resumed immediately via symmetric transfer,
203 bypassing the queue. This provides optimal performance but
204 means the coroutine may execute before previously queued work.
205
206 Otherwise, the coroutine is queued and will execute in FIFO
207 order relative to other queued coroutines.
208
209 @par Ordering
210 Callers requiring strict FIFO ordering should use post()
211 instead, which always queues the coroutine.
212
213 @param h The coroutine handle to dispatch.
214 @return A coroutine handle for symmetric transfer.
215 */
216 // TODO: measure before deciding to split strand_impl for inlining fast-path check
217 any_coro
218 3 dispatch(any_coro h) const
219 {
220
1/1
✓ Branch 2 taken 3 times.
3 return detail::strand_service::dispatch(*impl_, any_dispatcher(post_), h);
221 }
222
223 /** Post a coroutine to the strand.
224
225 The coroutine is always queued for execution, never resumed
226 immediately. When the strand becomes available, queued
227 coroutines execute in FIFO order on the underlying executor.
228
229 @par Ordering
230 Guarantees strict FIFO ordering relative to other post() calls.
231 Use this instead of dispatch() when ordering matters.
232
233 @param h The coroutine handle to post.
234 */
235 void
236 263 post(any_coro h) const
237 {
238
1/1
✓ Branch 2 taken 263 times.
263 detail::strand_service::post(*impl_, any_dispatcher(post_), h);
239 263 }
240
241 /** Defer a coroutine to the strand.
242
243 Equivalent to `post()`. The defer hint indicates that the
244 coroutine is a continuation of the current execution context,
245 but strands treat this the same as post.
246
247 @param h The coroutine handle to defer.
248 */
249 void
250 1 defer(any_coro h) const
251 {
252 1 post(h);
253 1 }
254
255 /** Dispatch a coroutine through the strand.
256
257 This operator provides a dispatcher-style interface for
258 use with symmetric transfer. Equivalent to `dispatch()`.
259
260 @param h The coroutine handle to dispatch.
261 @return A coroutine handle for symmetric transfer.
262 */
263 any_coro
264 1 operator()(any_coro h) const
265 {
266 1 return dispatch(h);
267 }
268 };
269
270 // Deduction guide
271 template<typename Executor>
272 strand(Executor) -> strand<Executor>;
273
274 } // namespace capy
275 } // namespace boost
276
277 #endif
278