include/boost/capy/ex/strand.hpp

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