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