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