GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/strand.hpp
Date: 2026-01-17 12:16:20
Exec Total Coverage
Lines: 27 27 100.0%
Functions: 11 11 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 - `operator()(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 /** Post a coroutine to the strand.
200
201 The coroutine is always queued for execution, never resumed
202 immediately. When the strand becomes available, queued
203 coroutines execute in FIFO order on the underlying executor.
204
205 @par Ordering
206 Guarantees strict FIFO ordering relative to other post() calls.
207 Use this instead of dispatch() when ordering matters.
208
209 @param h The coroutine handle to post.
210 */
211 void
212 263 post(any_coro h) const
213 {
214
1/1
✓ Branch 2 taken 263 times.
263 detail::strand_service::post(*impl_, any_dispatcher(post_), h);
215 263 }
216
217 /** Defer a coroutine to the strand.
218
219 Equivalent to `post()`. The defer hint indicates that the
220 coroutine is a continuation of the current execution context,
221 but strands treat this the same as post.
222
223 @param h The coroutine handle to defer.
224 */
225 void
226 1 defer(any_coro h) const
227 {
228 1 post(h);
229 1 }
230
231 /** Dispatch a coroutine through the strand.
232
233 If the calling thread is already executing within this strand,
234 the coroutine is resumed immediately via symmetric transfer,
235 bypassing the queue. This provides optimal performance but
236 means the coroutine may execute before previously queued work.
237
238 Otherwise, the coroutine is queued and will execute in FIFO
239 order relative to other queued coroutines.
240
241 @par Ordering
242 Callers requiring strict FIFO ordering should use post()
243 instead, which always queues the coroutine.
244
245 @param h The coroutine handle to dispatch.
246 @return A coroutine handle for symmetric transfer.
247 */
248 // TODO: measure before deciding to split strand_impl for inlining fast-path check
249 any_coro
250 3 operator()(any_coro h) const
251 {
252
1/1
✓ Branch 2 taken 3 times.
3 return detail::strand_service::dispatch(*impl_, any_dispatcher(post_), h);
253 }
254 };
255
256 // Deduction guide
257 template<typename Executor>
258 strand(Executor) -> strand<Executor>;
259
260 } // namespace capy
261 } // namespace boost
262
263 #endif
264