include/boost/capy/ex/strand.hpp

100.0% Lines (28/28) 86.5% List of functions (32/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&) :119 4x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand<boost::capy::executor_ref&, void>(boost::capy::executor_ref&) :119 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>&) :119 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&) :119 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&&) :119 11436x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::strand(boost::capy::strand<boost::capy::any_executor> const&) :132 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand(boost::capy::strand<boost::capy::executor_ref> const&) :132 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&) :132 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&) :132 6x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand(boost::capy::strand<boost::capy::executor_ref>&&) :139 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> >&&) :139 5x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::strand(boost::capy::strand<boost::capy::thread_pool::executor_type>&&) :139 11433x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator=(boost::capy::strand<boost::capy::thread_pool::executor_type> const&) :143 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator=(boost::capy::strand<boost::capy::thread_pool::executor_type>&&) :150 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::get_inner_executor() const :157 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::context() const :168 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::context() const :168 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::context() const :168 4x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::on_work_started() const :179 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::on_work_started() const :179 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_started() const :179 5x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::on_work_finished() const :190 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::on_work_finished() const :190 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_finished() const :190 5x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::running_in_this_thread() const :201 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::running_in_this_thread() const :201 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::running_in_this_thread() const :201 2x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::operator==(boost::capy::strand<boost::capy::any_executor> const&) const :216 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::operator==(boost::capy::strand<boost::capy::executor_ref> const&) const :216 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 :216 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 :216 499504x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::post(boost::capy::continuation&) const :241 20x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::post(boost::capy::continuation&) const :241 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::post(boost::capy::continuation&) const :241 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::post(boost::capy::continuation&) const :241 30313x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::dispatch(boost::capy::continuation&) const :269 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::dispatch(boost::capy::continuation&) const :269 6x 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 Each strand allocates a private serialization state. Strands
40 constructed from the same execution context share a small pool
41 of mutexes (193 entries) selected by hash; mutex sharing causes
42 only brief contention on the push/pop critical section, never
43 cross-strand state sharing. Construction cost: one
44 `std::make_shared` per strand.
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(continuation&)` - May run immediately if already executing in this strand
51 - `post(continuation&)` - Always queues for later execution
52
53 @par Preconditions
54 A strand holds only a non-owning reference to its inner executor's
55 execution context (for example a `thread_pool`). That context must
56 outlive every post() and dispatch() call; posting or dispatching
57 concurrently with, or after, the context's destruction is undefined
58 behavior. To guarantee this, submit work through @ref run_async or
59 @ref run — whose operations are work-tracked, so the context's
60 `join()` waits for them — and call `join()` on the context before
61 destroying it, rather than posting to a strand from an external
62 thread the context does not track. Destroying the strand handle
63 itself is always safe, including after the context has been
64 destroyed.
65
66 @par Thread Safety
67 Distinct objects: Safe.
68 Shared objects: Safe.
69
70 @par Example
71 @code
72 thread_pool pool(4);
73 strand strand(pool.get_executor()); // CTAD deduces the executor type
74
75 // Continuations are linked intrusively into the strand's queue,
76 // so each one must outlive its time there. Storage is typically
77 // owned by the awaitable or operation state that posted it.
78 continuation c1{h1}, c2{h2}, c3{h3};
79 strand.post(c1);
80 strand.post(c2);
81 strand.post(c3);
82 @endcode
83
84 @tparam Ex The type of the underlying executor. Must
85 satisfy the `Executor` concept.
86
87 @see Executor
88 */
89 template<typename Ex>
90 class strand
91 {
92 std::shared_ptr<detail::strand_impl> impl_;
93 Ex ex_;
94
95 friend struct strand_test;
96
97 public:
98 /** The type of the underlying executor.
99 */
100 using inner_executor_type = Ex;
101
102 /** Construct a strand for the specified executor.
103
104 Allocates a fresh strand implementation from the service
105 associated with the executor's context.
106
107 @param ex The inner executor to wrap. Coroutines will
108 ultimately be dispatched through this executor.
109
110 @note This constructor is disabled if the argument is a
111 strand type, to prevent strand-of-strand wrapping.
112 */
113 template<typename Ex1,
114 typename = std::enable_if_t<
115 !std::is_same_v<std::decay_t<Ex1>, strand> &&
116 !detail::is_strand<std::decay_t<Ex1>>::value &&
117 std::is_convertible_v<Ex1, Ex>>>
118 explicit
119 11443x strand(Ex1&& ex)
120 11443x : impl_(detail::get_strand_service(ex.context())
121 11443x .create_implementation())
122 11443x , ex_(std::forward<Ex1>(ex))
123 {
124 11443x }
125
126 /** Construct a copy.
127
128 Creates a strand that shares serialization state with
129 the original. Coroutines dispatched through either strand
130 will be serialized with respect to each other.
131 */
132 9x strand(strand const&) = default;
133
134 /** Construct by moving.
135
136 @note A moved-from strand is only safe to destroy
137 or reassign.
138 */
139 11443x strand(strand&&) = default;
140
141 /** Assign by copying.
142 */
143 1x strand& operator=(strand const&) = default;
144
145 /** Assign by moving.
146
147 @note A moved-from strand is only safe to destroy
148 or reassign.
149 */
150 1x strand& operator=(strand&&) = default;
151
152 /** Return the underlying executor.
153
154 @return A const reference to the inner executor.
155 */
156 Ex const&
157 1x get_inner_executor() const noexcept
158 {
159 1x return ex_;
160 }
161
162 /** Return the underlying execution context.
163
164 @return A reference to the execution context associated
165 with the inner executor.
166 */
167 auto&
168 5x context() const noexcept
169 {
170 5x return ex_.context();
171 }
172
173 /** Notify that work has started.
174
175 Delegates to the inner executor's `on_work_started()`.
176 This is a no-op for most executor types.
177 */
178 void
179 6x on_work_started() const noexcept
180 {
181 6x ex_.on_work_started();
182 6x }
183
184 /** Notify that work has finished.
185
186 Delegates to the inner executor's `on_work_finished()`.
187 This is a no-op for most executor types.
188 */
189 void
190 6x on_work_finished() const noexcept
191 {
192 6x ex_.on_work_finished();
193 6x }
194
195 /** Determine whether the strand is running in the current thread.
196
197 @return true if the current thread is executing a coroutine
198 within this strand's dispatch loop.
199 */
200 bool
201 4x running_in_this_thread() const noexcept
202 {
203 4x return detail::strand_service::running_in_this_thread(*impl_);
204 }
205
206 /** Compare two strands for equality.
207
208 Two strands are equal if they share the same internal
209 serialization state. Equal strands serialize coroutines
210 with respect to each other.
211
212 @param other The strand to compare against.
213 @return true if both strands share the same implementation.
214 */
215 bool
216 499505x operator==(strand const& other) const noexcept
217 {
218 499505x return impl_.get() == other.impl_.get();
219 }
220
221 /** Post a continuation to the strand.
222
223 The continuation is always queued for execution, never resumed
224 immediately. When the strand becomes available, queued
225 work executes in FIFO order on the underlying executor.
226
227 @par Ordering
228 Guarantees strict FIFO ordering relative to other post() calls.
229 Use this instead of dispatch() when ordering matters.
230
231 @param c The continuation to post. The caller retains
232 ownership; the continuation must remain valid until
233 it is dequeued and resumed.
234
235 @par Preconditions
236 The strand's execution context must outlive this call. Posting
237 concurrently with, or after, that context's destruction is
238 undefined behavior.
239 */
240 void
241 30335x post(continuation& c) const
242 {
243 30335x detail::strand_service::post(impl_, executor_ref(ex_), c);
244 30335x }
245
246 /** Dispatch a continuation through the strand.
247
248 Returns a handle for symmetric transfer. If the calling
249 thread is already executing within this strand, returns `c.h`.
250 Otherwise, the continuation is queued and
251 `std::noop_coroutine()` is returned.
252
253 @par Ordering
254 Callers requiring strict FIFO ordering should use post()
255 instead, which always queues the continuation.
256
257 @param c The continuation to dispatch. The caller retains
258 ownership; the continuation must remain valid until
259 it is dequeued and resumed.
260
261 @return A handle for symmetric transfer or `std::noop_coroutine()`.
262
263 @par Preconditions
264 The strand's execution context must outlive this call.
265 Dispatching concurrently with, or after, that context's
266 destruction is undefined behavior.
267 */
268 std::coroutine_handle<>
269 8x dispatch(continuation& c) const
270 {
271 8x return detail::strand_service::dispatch(impl_, executor_ref(ex_), c);
272 }
273 };
274
275 // Deduction guide
276 template<typename Ex>
277 strand(Ex) -> strand<Ex>;
278
279 } // namespace capy
280 } // namespace boost
281
282 #endif
283