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&) :104 4x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand<boost::capy::executor_ref&, void>(boost::capy::executor_ref&) :104 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>&) :104 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&) :104 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&&) :104 11435x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::strand(boost::capy::strand<boost::capy::any_executor> const&) :117 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand(boost::capy::strand<boost::capy::executor_ref> const&) :117 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&) :117 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&) :117 6x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::strand(boost::capy::strand<boost::capy::executor_ref>&&) :124 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> >&&) :124 5x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::strand(boost::capy::strand<boost::capy::thread_pool::executor_type>&&) :124 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&) :128 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::operator=(boost::capy::strand<boost::capy::thread_pool::executor_type>&&) :135 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::get_inner_executor() const :142 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::context() const :153 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::context() const :153 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::context() const :153 4x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::on_work_started() const :164 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::on_work_started() const :164 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_started() const :164 5x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::on_work_finished() const :175 0 0.0% 0.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::on_work_finished() const :175 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::on_work_finished() const :175 5x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::running_in_this_thread() const :186 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::running_in_this_thread() const :186 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::running_in_this_thread() const :186 2x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::operator==(boost::capy::strand<boost::capy::any_executor> const&) const :201 1x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::operator==(boost::capy::strand<boost::capy::executor_ref> const&) const :201 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 :201 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 :201 499504x 100.0% 100.0% boost::capy::strand<boost::capy::any_executor>::post(boost::capy::continuation&) const :221 20x 100.0% 100.0% boost::capy::strand<boost::capy::executor_ref>::post(boost::capy::continuation&) const :221 1x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::post(boost::capy::continuation&) const :221 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::post(boost::capy::continuation&) const :221 30313x 100.0% 100.0% boost::capy::strand<boost::capy::test::priority_executor<boost::capy::queuing_executor> >::dispatch(boost::capy::continuation&) const :244 1x 100.0% 100.0% boost::capy::strand<boost::capy::thread_pool::executor_type>::dispatch(boost::capy::continuation&) const :244 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 strand is idle
51 - `post(continuation&)` - Always queues for later execution
52
53 @par Thread Safety
54 Distinct objects: Safe.
55 Shared objects: Safe.
56
57 @par Example
58 @code
59 thread_pool pool(4);
60 auto strand = make_strand(pool.get_executor());
61
62 // These continuations will never run concurrently
63 continuation c1{h1}, c2{h2}, c3{h3};
64 strand.post(c1);
65 strand.post(c2);
66 strand.post(c3);
67 @endcode
68
69 @tparam E The type of the underlying executor. Must
70 satisfy the `Executor` concept.
71
72 @see make_strand, Executor
73 */
74 template<typename Ex>
75 class strand
76 {
77 std::shared_ptr<detail::strand_impl> impl_;
78 Ex ex_;
79
80 friend struct strand_test;
81
82 public:
83 /** The type of the underlying executor.
84 */
85 using inner_executor_type = Ex;
86
87 /** Construct a strand for the specified executor.
88
89 Allocates a fresh strand implementation from the service
90 associated with the executor's context.
91
92 @param ex The inner executor to wrap. Coroutines will
93 ultimately be dispatched through this executor.
94
95 @note This constructor is disabled if the argument is a
96 strand type, to prevent strand-of-strand wrapping.
97 */
98 template<typename Ex1,
99 typename = std::enable_if_t<
100 !std::is_same_v<std::decay_t<Ex1>, strand> &&
101 !detail::is_strand<std::decay_t<Ex1>>::value &&
102 std::is_convertible_v<Ex1, Ex>>>
103 explicit
104 11442x strand(Ex1&& ex)
105 11442x : impl_(detail::get_strand_service(ex.context())
106 11442x .create_implementation())
107 11442x , ex_(std::forward<Ex1>(ex))
108 {
109 11442x }
110
111 /** Construct a copy.
112
113 Creates a strand that shares serialization state with
114 the original. Coroutines dispatched through either strand
115 will be serialized with respect to each other.
116 */
117 9x strand(strand const&) = default;
118
119 /** Construct by moving.
120
121 @note A moved-from strand is only safe to destroy
122 or reassign.
123 */
124 11443x strand(strand&&) = default;
125
126 /** Assign by copying.
127 */
128 1x strand& operator=(strand const&) = default;
129
130 /** Assign by moving.
131
132 @note A moved-from strand is only safe to destroy
133 or reassign.
134 */
135 1x strand& operator=(strand&&) = default;
136
137 /** Return the underlying executor.
138
139 @return A const reference to the inner executor.
140 */
141 Ex const&
142 1x get_inner_executor() const noexcept
143 {
144 1x return ex_;
145 }
146
147 /** Return the underlying execution context.
148
149 @return A reference to the execution context associated
150 with the inner executor.
151 */
152 auto&
153 5x context() const noexcept
154 {
155 5x return ex_.context();
156 }
157
158 /** Notify that work has started.
159
160 Delegates to the inner executor's `on_work_started()`.
161 This is a no-op for most executor types.
162 */
163 void
164 6x on_work_started() const noexcept
165 {
166 6x ex_.on_work_started();
167 6x }
168
169 /** Notify that work has finished.
170
171 Delegates to the inner executor's `on_work_finished()`.
172 This is a no-op for most executor types.
173 */
174 void
175 6x on_work_finished() const noexcept
176 {
177 6x ex_.on_work_finished();
178 6x }
179
180 /** Determine whether the strand is running in the current thread.
181
182 @return true if the current thread is executing a coroutine
183 within this strand's dispatch loop.
184 */
185 bool
186 4x running_in_this_thread() const noexcept
187 {
188 4x return detail::strand_service::running_in_this_thread(*impl_);
189 }
190
191 /** Compare two strands for equality.
192
193 Two strands are equal if they share the same internal
194 serialization state. Equal strands serialize coroutines
195 with respect to each other.
196
197 @param other The strand to compare against.
198 @return true if both strands share the same implementation.
199 */
200 bool
201 499505x operator==(strand const& other) const noexcept
202 {
203 499505x return impl_.get() == other.impl_.get();
204 }
205
206 /** Post a continuation to the strand.
207
208 The continuation is always queued for execution, never resumed
209 immediately. When the strand becomes available, queued
210 work executes in FIFO order on the underlying executor.
211
212 @par Ordering
213 Guarantees strict FIFO ordering relative to other post() calls.
214 Use this instead of dispatch() when ordering matters.
215
216 @param c The continuation to post. The caller retains
217 ownership; the continuation must remain valid until
218 it is dequeued and resumed.
219 */
220 void
221 30335x post(continuation& c) const
222 {
223 30335x detail::strand_service::post(impl_, executor_ref(ex_), c.h);
224 30335x }
225
226 /** Dispatch a continuation through the strand.
227
228 Returns a handle for symmetric transfer. If the calling
229 thread is already executing within this strand, returns `c.h`.
230 Otherwise, the continuation is queued and
231 `std::noop_coroutine()` is returned.
232
233 @par Ordering
234 Callers requiring strict FIFO ordering should use post()
235 instead, which always queues the continuation.
236
237 @param c The continuation to dispatch. The caller retains
238 ownership; the continuation must remain valid until
239 it is dequeued and resumed.
240
241 @return A handle for symmetric transfer or `std::noop_coroutine()`.
242 */
243 std::coroutine_handle<>
244 8x dispatch(continuation& c) const
245 {
246 8x return detail::strand_service::dispatch(impl_, executor_ref(ex_), c.h);
247 }
248 };
249
250 // Deduction guide
251 template<typename Ex>
252 strand(Ex) -> strand<Ex>;
253
254 } // namespace capy
255 } // namespace boost
256
257 #endif
258