include/boost/capy/ex/strand.hpp

100.0% Lines (28/0/28) 86.5% List of functions (32/0/37)
strand.hpp
f(x) Functions (37)
Function Calls Lines Blocks
boost::capy::strand<boost::capy::any_executor>::strand<boost::capy::any_executor&, void>(boost::capy::any_executor&) :120 4x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand<boost::capy::executor_ref&, void>(boost::capy::executor_ref&) :120 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor>&, void>(boost::capy::test::priority_executor<boost::capy::queuing_executor>&) :120 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&) :120 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&&) :120 11439x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::strand(boost::capy::strand<boost::capy::any_executor> const&) :135 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand(boost::capy::strand<boost::capy::executor_ref> const&) :135 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::strand(boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> > const&) :135 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::strand(boost::capy::strand<boost::capy::thread_pool::executor_type> const&) :135 8x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand(boost::capy::strand<boost::capy::executor_ref>&&) :144 5x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::strand(boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >&&) :144 5x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::strand(boost::capy::strand<boost::capy::thread_pool::executor_type>&&) :144 11443x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator=(boost::capy::strand<boost::capy::thread_pool::executor_type> const&) :155 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator=(boost::capy::strand<boost::capy::thread_pool::executor_type>&&) :166 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::get_inner_executor() const :173 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::context() const :184 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::context() const :184 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::context() const :184 5x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::on_work_started() const :196 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::on_work_started() const :196 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_started() const :196 6x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::on_work_finished() const :208 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::on_work_finished() const :208 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_finished() const :208 6x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::running_in_this_thread() const :219 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::running_in_this_thread() const :219 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::running_in_this_thread() const :219 2x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::operator==(boost::capy::strand<boost::capy::any_executor> const&) const :234 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::operator==(boost::capy::strand<boost::capy::executor_ref> const&) const :234 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::operator==(boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> > const&) const :234 0 0.0% 0.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator==(boost::capy::strand<boost::capy::thread_pool::executor_type> const&) const :234 499504x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::post(boost::capy::continuation&) const :259 20x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::post(boost::capy::continuation&) const :259 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::post(boost::capy::continuation&) const :259 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::post(boost::capy::continuation&) const :259 30314x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::dispatch(boost::capy::continuation&) const :287 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::dispatch(boost::capy::continuation&) const :287 7x 100.0% 100.0%
Line TLA Hits 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 @par Thread Safety
68 Distinct objects: Safe.
69 Shared objects: Safe.
70
71 @par Example
72 @code
73 thread_pool pool(4);
74 strand strand(pool.get_executor()); // CTAD deduces the executor type
75
76 // Continuations are linked intrusively into the strand's queue,
77 // so each one must outlive its time there. Storage is typically
78 // owned by the awaitable or operation state that posted it.
79 continuation c1{h1}, c2{h2}, c3{h3};
80 strand.post(c1);
81 strand.post(c2);
82 strand.post(c3);
83 @endcode
84
85 @tparam Ex The type of the underlying executor. Must
86 satisfy the `Executor` concept.
87
88 @see Executor
89 */
90 template<typename Ex>
91 class strand
92 {
93 std::shared_ptr<detail::strand_impl> impl_;
94 Ex ex_;
95
96 friend struct strand_test;
97
98 public:
99 /** Names the executor type this `strand<Ex>` wraps.
100 */
101 using inner_executor_type = Ex;
102
103 /** Construct a strand for the specified executor.
104
105 Allocates a fresh strand implementation from the service
106 associated with the executor's context.
107
108 @param ex The inner executor to wrap. Coroutines are
109 ultimately dispatched through this executor.
110
111 @note This constructor is disabled if the argument is a
112 strand type, to prevent strand-of-strand wrapping.
113 */
114 template<typename Ex1,
115 typename = std::enable_if_t<
116 !std::is_same_v<std::decay_t<Ex1>, strand> &&
117 !detail::is_strand<std::decay_t<Ex1>>::value &&
118 std::is_convertible_v<Ex1, Ex>>>
119 explicit
120 11446x strand(Ex1&& ex)
121 11446x : impl_(detail::get_strand_service(ex.context())
122 11446x .create_implementation())
123 11446x , ex_(std::forward<Ex1>(ex))
124 {
125 11446x }
126
127 /** Construct a copy.
128
129 Creates a strand that shares serialization state with
130 the original. Coroutines dispatched through either strand
131 are serialized with respect to each other.
132
133 @param other The strand to copy.
134 */
135 11x strand(strand const& other) = default;
136
137 /** Construct by moving.
138
139 @param other The strand to move from.
140
141 @note A moved-from strand is only safe to destroy
142 or reassign.
143 */
144 11453x strand(strand&& other) = default;
145
146 /** Assign by copying.
147
148 Shares serialization state with `other`, as the copy
149 constructor does.
150
151 @param other The strand to copy.
152
153 @return A reference to `*this`.
154 */
155 1x strand& operator=(strand const& other) = default;
156
157 /** Assign by moving.
158
159 @param other The strand to move from.
160
161 @return A reference to `*this`.
162
163 @note A moved-from strand is only safe to destroy
164 or reassign.
165 */
166 1x strand& operator=(strand&& other) = default;
167
168 /** Return the underlying executor.
169
170 @return A const reference to the inner executor.
171 */
172 Ex const&
173 1x get_inner_executor() const noexcept
174 {
175 1x return ex_;
176 }
177
178 /** Return the underlying execution context.
179
180 @return A reference to the execution context associated
181 with the inner executor.
182 */
183 auto&
184 6x context() const noexcept
185 {
186 6x return ex_.context();
187 }
188
189 /** Notify that work has started.
190
191 Delegates to the inner executor's `on_work_started()`. For a
192 `thread_pool` inner executor, this increments the count that
193 `join()` blocks on.
194 */
195 void
196 7x on_work_started() const noexcept
197 {
198 7x ex_.on_work_started();
199 7x }
200
201 /** Notify that work has finished.
202
203 Delegates to the inner executor's `on_work_finished()`. For a
204 `thread_pool` inner executor, this decrements the count that
205 `join()` blocks on.
206 */
207 void
208 7x on_work_finished() const noexcept
209 {
210 7x ex_.on_work_finished();
211 7x }
212
213 /** Determine whether the strand is running in the current thread.
214
215 @return true if the current thread is executing a coroutine
216 within this strand's dispatch loop.
217 */
218 bool
219 4x running_in_this_thread() const noexcept
220 {
221 4x return detail::strand_service::running_in_this_thread(*impl_);
222 }
223
224 /** Compare two strands for equality.
225
226 Two strands are equal if they share the same internal
227 serialization state. Equal strands serialize coroutines
228 with respect to each other.
229
230 @param other The strand to compare against.
231 @return true if both strands share the same implementation.
232 */
233 bool
234 499505x operator==(strand const& other) const noexcept
235 {
236 499505x return impl_.get() == other.impl_.get();
237 }
238
239 /** Post a continuation to the strand.
240
241 The continuation is always queued for execution, never resumed
242 immediately. When the strand becomes available, queued
243 work executes in FIFO order on the underlying executor.
244
245 @par Ordering
246 Guarantees strict FIFO ordering relative to other post() calls.
247 Use this instead of dispatch() when ordering matters.
248
249 @param c The continuation to post. The caller retains
250 ownership; the continuation must remain valid until
251 it is dequeued and resumed.
252
253 @par Preconditions
254 The strand's execution context must outlive this call. Posting
255 concurrently with, or after, that context's destruction is
256 undefined behavior.
257 */
258 void
259 30336x post(continuation& c) const
260 {
261 30336x detail::strand_service::post(impl_, executor_ref(ex_), c);
262 30336x }
263
264 /** Dispatch a continuation through the strand.
265
266 Returns a handle for symmetric transfer. If the calling
267 thread is already executing within this strand, returns `c.h`.
268 Otherwise, the continuation is queued and
269 `std::noop_coroutine()` is returned.
270
271 @par Ordering
272 Callers requiring strict FIFO ordering should use post()
273 instead, which always queues the continuation.
274
275 @param c The continuation to dispatch. The caller retains
276 ownership; the continuation must remain valid until
277 it is dequeued and resumed.
278
279 @return A handle for symmetric transfer or `std::noop_coroutine()`.
280
281 @par Preconditions
282 The strand's execution context must outlive this call.
283 Dispatching concurrently with, or after, that context's
284 destruction is undefined behavior.
285 */
286 std::coroutine_handle<>
287 9x dispatch(continuation& c) const
288 {
289 9x return detail::strand_service::dispatch(impl_, executor_ref(ex_), c);
290 }
291 };
292
293 /** Deduce the executor type from the constructor argument.
294
295 @tparam Ex The wrapped executor type.
296 */
297 template<typename Ex>
298 strand(Ex) -> strand<Ex>;
299
300 } // namespace capy
301 } // namespace boost
302
303 #endif
304