include/boost/capy/ex/strand.hpp

100.0% Lines (40/40) 100.0% List of functions (14/14)
strand.hpp
f(x) Functions (14)
Function Calls Lines Blocks
boost::capy::strand<boost::capy::any_executor>::strand<boost::capy::any_executor&, void>(boost::capy::any_executor&) :102 4x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::strand<boost::capy::thread_pool::executor_type&, void>(boost::capy::thread_pool::executor_type&) :102 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::strand<boost::capy::thread_pool::executor_type, void>(boost::capy::thread_pool::executor_type&&) :102 22x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::strand(boost::capy::strand<boost::capy::any_executor> const&) :115 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::get_inner_executor() const :140 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::context() const :151 4x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_started() const :162 5x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_finished() const :173 5x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::running_in_this_thread() const :184 2x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::operator==(boost::capy::strand<boost::capy::any_executor> const&) const :199 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator==(boost::capy::strand<boost::capy::thread_pool::executor_type> const&) const :199 3x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::post(boost::capy::continuation&) const :219 20x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::post(boost::capy::continuation&) const :219 307x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::dispatch(boost::capy::continuation&) const :242 8x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/continuation.hpp>
15 #include <coroutine>
16 #include <boost/capy/ex/detail/strand_service.hpp>
17
18 #include <type_traits>
19
20 namespace boost {
21 namespace capy {
22
23 /** Provides serialized coroutine execution for any executor type.
24
25 A strand wraps an inner executor and ensures that coroutines
26 dispatched through it never run concurrently. At most one
27 coroutine executes at a time within a strand, even when the
28 underlying executor runs on multiple threads.
29
30 Strands are lightweight handles that can be copied freely.
31 Copies share the same internal serialization state, so
32 coroutines dispatched through any copy are serialized with
33 respect to all other copies.
34
35 @par Invariant
36 Coroutines resumed through a strand shall not run concurrently.
37
38 @par Implementation
39 The strand uses a service-based architecture with a fixed pool
40 of 211 implementation objects. New strands hash to select an
41 impl from the pool. Strands that hash to the same index share
42 serialization, which is harmless (just extra serialization)
43 and rare with 211 buckets.
44
45 @par Executor Concept
46 This class satisfies the `Executor` concept, providing:
47 - `context()` - Returns the underlying execution context
48 - `on_work_started()` / `on_work_finished()` - Work tracking
49 - `dispatch(continuation&)` - May run immediately if strand is idle
50 - `post(continuation&)` - Always queues for later execution
51
52 @par Thread Safety
53 Distinct objects: Safe.
54 Shared objects: Safe.
55
56 @par Example
57 @code
58 thread_pool pool(4);
59 auto strand = make_strand(pool.get_executor());
60
61 // These continuations will never run concurrently
62 continuation c1{h1}, c2{h2}, c3{h3};
63 strand.post(c1);
64 strand.post(c2);
65 strand.post(c3);
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 27x strand(Ex1&& ex)
103 27x : impl_(detail::get_strand_service(ex.context())
104 27x .get_implementation())
105 27x , ex_(std::forward<Ex1>(ex))
106 {
107 27x }
108
109 /** Construct a copy.
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 1x strand(strand const&) = default;
116
117 /** Construct by moving.
118
119 @note A moved-from strand is only safe to destroy
120 or reassign.
121 */
122 strand(strand&&) = default;
123
124 /** Assign by copying.
125 */
126 strand& operator=(strand const&) = default;
127
128 /** Assign by moving.
129
130 @note A moved-from strand is only safe to destroy
131 or reassign.
132 */
133 strand& operator=(strand&&) = default;
134
135 /** Return the underlying executor.
136
137 @return A const reference to the inner executor.
138 */
139 Ex const&
140 1x get_inner_executor() const noexcept
141 {
142 1x return ex_;
143 }
144
145 /** Return the underlying execution context.
146
147 @return A reference to the execution context associated
148 with the inner executor.
149 */
150 auto&
151 4x context() const noexcept
152 {
153 4x return ex_.context();
154 }
155
156 /** Notify that work has started.
157
158 Delegates to the inner executor's `on_work_started()`.
159 This is a no-op for most executor types.
160 */
161 void
162 5x on_work_started() const noexcept
163 {
164 5x ex_.on_work_started();
165 5x }
166
167 /** Notify that work has finished.
168
169 Delegates to the inner executor's `on_work_finished()`.
170 This is a no-op for most executor types.
171 */
172 void
173 5x on_work_finished() const noexcept
174 {
175 5x ex_.on_work_finished();
176 5x }
177
178 /** Determine whether the strand is running in the current thread.
179
180 @return true if the current thread is executing a coroutine
181 within this strand's dispatch loop.
182 */
183 bool
184 2x running_in_this_thread() const noexcept
185 {
186 2x return detail::strand_service::running_in_this_thread(*impl_);
187 }
188
189 /** Compare two strands for equality.
190
191 Two strands are equal if they share the same internal
192 serialization state. Equal strands serialize coroutines
193 with respect to each other.
194
195 @param other The strand to compare against.
196 @return true if both strands share the same implementation.
197 */
198 bool
199 4x operator==(strand const& other) const noexcept
200 {
201 4x return impl_ == other.impl_;
202 }
203
204 /** Post a continuation to the strand.
205
206 The continuation is always queued for execution, never resumed
207 immediately. When the strand becomes available, queued
208 work executes in FIFO order on the underlying executor.
209
210 @par Ordering
211 Guarantees strict FIFO ordering relative to other post() calls.
212 Use this instead of dispatch() when ordering matters.
213
214 @param c The continuation to post. The caller retains
215 ownership; the continuation must remain valid until
216 it is dequeued and resumed.
217 */
218 void
219 327x post(continuation& c) const
220 {
221 327x detail::strand_service::post(*impl_, executor_ref(ex_), c.h);
222 327x }
223
224 /** Dispatch a continuation through the strand.
225
226 Returns a handle for symmetric transfer. If the calling
227 thread is already executing within this strand, returns `c.h`.
228 Otherwise, the continuation is queued and
229 `std::noop_coroutine()` is returned.
230
231 @par Ordering
232 Callers requiring strict FIFO ordering should use post()
233 instead, which always queues the continuation.
234
235 @param c The continuation to dispatch. The caller retains
236 ownership; the continuation must remain valid until
237 it is dequeued and resumed.
238
239 @return A handle for symmetric transfer or `std::noop_coroutine()`.
240 */
241 std::coroutine_handle<>
242 8x dispatch(continuation& c) const
243 {
244 8x return detail::strand_service::dispatch(*impl_, executor_ref(ex_), c.h);
245 }
246 };
247
248 // Deduction guide
249 template<typename Ex>
250 strand(Ex) -> strand<Ex>;
251
252 } // namespace capy
253 } // namespace boost
254
255 #endif
256