GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/strand.hpp
Date: 2026-01-18 18:26:31
Exec Total Coverage
Lines: 24 24 100.0%
Functions: 10 10 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
53 @par Thread Safety
54 Distinct objects: Safe.
55 Shared objects: Safe.
56
57 @par Example
58 @code
59 thread_pool pool(4);
60 auto strand = make_strand(pool.get_executor());
61
62 // These coroutines will never run concurrently
63 strand.post(coro1);
64 strand.post(coro2);
65 strand.post(coro3);
66 @endcode
67
68 @tparam E The type of the underlying executor. Must
69 satisfy the `Executor` concept.
70
71 @see make_strand, Executor
72 */
73 template<typename Ex>
74 class strand
75 {
76 detail::strand_impl* impl_;
77 Ex ex_;
78
79 public:
80 /** The type of the underlying executor.
81 */
82 using inner_executor_type = Ex;
83
84 /** Construct a strand for the specified executor.
85
86 Obtains a strand implementation from the service associated
87 with the executor's context. The implementation is selected
88 from a fixed pool using a hash function.
89
90 @param ex The inner executor to wrap. Coroutines will
91 ultimately be dispatched through this executor.
92
93 @note This constructor is disabled if the argument is a
94 strand type, to prevent strand-of-strand wrapping.
95 */
96 template<typename Ex1,
97 typename = std::enable_if_t<
98 !std::is_same_v<std::decay_t<Ex1>, strand> &&
99 !detail::is_strand<std::decay_t<Ex1>>::value &&
100 std::is_convertible_v<Ex1, Ex>>>
101 explicit
102 46 strand(Ex1&& ex)
103 46 : impl_(detail::get_strand_service(ex.context())
104 46 .get_implementation())
105 46 , ex_(std::forward<Ex1>(ex))
106 {
107 46 }
108
109 /** Copy constructor.
110
111 Creates a strand that shares serialization state with
112 the original. Coroutines dispatched through either strand
113 will be serialized with respect to each other.
114 */
115 strand(strand const&) = default;
116
117 /** Move constructor.
118 */
119 strand(strand&&) = default;
120
121 /** Copy assignment operator.
122 */
123 strand& operator=(strand const&) = default;
124
125 /** Move assignment operator.
126 */
127 strand& operator=(strand&&) = default;
128
129 /** Return the underlying executor.
130
131 @return A const reference to the inner executor.
132 */
133 Ex const&
134 1 get_inner_executor() const noexcept
135 {
136 1 return ex_;
137 }
138
139 /** Return the underlying execution context.
140
141 @return A reference to the execution context associated
142 with the inner executor.
143 */
144 auto&
145 1 context() const noexcept
146 {
147 1 return ex_.context();
148 }
149
150 /** Notify that work has started.
151
152 Delegates to the inner executor's `on_work_started()`.
153 This is a no-op for most executor types.
154 */
155 void
156 2 on_work_started() const noexcept
157 {
158 2 ex_.on_work_started();
159 2 }
160
161 /** Notify that work has finished.
162
163 Delegates to the inner executor's `on_work_finished()`.
164 This is a no-op for most executor types.
165 */
166 void
167 2 on_work_finished() const noexcept
168 {
169 2 ex_.on_work_finished();
170 2 }
171
172 /** Determine whether the strand is running in the current thread.
173
174 @return true if the current thread is executing a coroutine
175 within this strand's dispatch loop.
176 */
177 bool
178 1 running_in_this_thread() const noexcept
179 {
180 1 return detail::strand_service::running_in_this_thread(*impl_);
181 }
182
183 /** Compare two strands for equality.
184
185 Two strands are equal if they share the same internal
186 serialization state. Equal strands serialize coroutines
187 with respect to each other.
188
189 @param other The strand to compare against.
190 @return true if both strands share the same implementation.
191 */
192 bool
193 3 operator==(strand const& other) const noexcept
194 {
195 3 return impl_ == other.impl_;
196 }
197
198 /** Post a coroutine to the strand.
199
200 The coroutine is always queued for execution, never resumed
201 immediately. When the strand becomes available, queued
202 coroutines execute in FIFO order on the underlying executor.
203
204 @par Ordering
205 Guarantees strict FIFO ordering relative to other post() calls.
206 Use this instead of dispatch() when ordering matters.
207
208 @param h The coroutine handle to post.
209 */
210 void
211 262 post(any_coro h) const
212 {
213
1/1
✓ Branch 2 taken 262 times.
262 detail::strand_service::post(*impl_, any_executor_ref(ex_), h);
214 262 }
215
216 /** Dispatch a coroutine through the strand.
217
218 If the calling thread is already executing within this strand,
219 the coroutine is resumed immediately via symmetric transfer,
220 bypassing the queue. This provides optimal performance but
221 means the coroutine may execute before previously queued work.
222
223 Otherwise, the coroutine is queued and will execute in FIFO
224 order relative to other queued coroutines.
225
226 @par Ordering
227 Callers requiring strict FIFO ordering should use post()
228 instead, which always queues the coroutine.
229
230 @param h The coroutine handle to dispatch.
231 @return A coroutine handle for symmetric transfer.
232 */
233 any_coro
234 3 dispatch(any_coro h) const
235 {
236
1/1
✓ Branch 2 taken 3 times.
3 return detail::strand_service::dispatch(*impl_, any_executor_ref(ex_), h);
237 }
238 };
239
240 // Deduction guide
241 template<typename Ex>
242 strand(Ex) -> strand<Ex>;
243
244 } // namespace capy
245 } // namespace boost
246
247 #endif
248