Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/ex/any_coro.hpp>
15 : #include <boost/capy/ex/detail/strand_service.hpp>
16 :
17 : #include <type_traits>
18 :
19 : namespace boost {
20 : namespace capy {
21 :
22 : //----------------------------------------------------------
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 : The strand uses a service-based architecture with a fixed pool
41 : of 211 implementation objects. New strands hash to select an
42 : impl from the pool. Strands that hash to the same index share
43 : serialization, which is harmless (just extra serialization)
44 : and rare with 211 buckets.
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(h)` - May run immediately if strand is idle
51 : - `post(h)` - 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 coroutines will never run concurrently
63 : strand.post(coro1);
64 : strand.post(coro2);
65 : strand.post(coro3);
66 : @endcode
67 :
68 : @tparam E The type of the underlying executor. Must
69 : satisfy the `Executor` concept.
70 :
71 : @see make_strand, Executor
72 : */
73 : template<typename Ex>
74 : class strand
75 : {
76 : detail::strand_impl* impl_;
77 : Ex ex_;
78 :
79 : public:
80 : /** The type of the underlying executor.
81 : */
82 : using inner_executor_type = Ex;
83 :
84 : /** Construct a strand for the specified executor.
85 :
86 : Obtains a strand implementation from the service associated
87 : with the executor's context. The implementation is selected
88 : from a fixed pool using a hash function.
89 :
90 : @param ex The inner executor to wrap. Coroutines will
91 : ultimately be dispatched through this executor.
92 :
93 : @note This constructor is disabled if the argument is a
94 : strand type, to prevent strand-of-strand wrapping.
95 : */
96 : template<typename Ex1,
97 : typename = std::enable_if_t<
98 : !std::is_same_v<std::decay_t<Ex1>, strand> &&
99 : !detail::is_strand<std::decay_t<Ex1>>::value &&
100 : std::is_convertible_v<Ex1, Ex>>>
101 : explicit
102 23 : strand(Ex1&& ex)
103 23 : : impl_(detail::get_strand_service(ex.context())
104 23 : .get_implementation())
105 23 : , ex_(std::forward<Ex1>(ex))
106 : {
107 23 : }
108 :
109 : /** Copy constructor.
110 :
111 : Creates a strand that shares serialization state with
112 : the original. Coroutines dispatched through either strand
113 : will be serialized with respect to each other.
114 : */
115 : strand(strand const&) = default;
116 :
117 : /** Move constructor.
118 : */
119 : strand(strand&&) = default;
120 :
121 : /** Copy assignment operator.
122 : */
123 : strand& operator=(strand const&) = default;
124 :
125 : /** Move assignment operator.
126 : */
127 : strand& operator=(strand&&) = default;
128 :
129 : /** Return the underlying executor.
130 :
131 : @return A const reference to the inner executor.
132 : */
133 : Ex const&
134 1 : get_inner_executor() const noexcept
135 : {
136 1 : return ex_;
137 : }
138 :
139 : /** Return the underlying execution context.
140 :
141 : @return A reference to the execution context associated
142 : with the inner executor.
143 : */
144 : auto&
145 1 : context() const noexcept
146 : {
147 1 : return ex_.context();
148 : }
149 :
150 : /** Notify that work has started.
151 :
152 : Delegates to the inner executor's `on_work_started()`.
153 : This is a no-op for most executor types.
154 : */
155 : void
156 2 : on_work_started() const noexcept
157 : {
158 2 : ex_.on_work_started();
159 2 : }
160 :
161 : /** Notify that work has finished.
162 :
163 : Delegates to the inner executor's `on_work_finished()`.
164 : This is a no-op for most executor types.
165 : */
166 : void
167 2 : on_work_finished() const noexcept
168 : {
169 2 : ex_.on_work_finished();
170 2 : }
171 :
172 : /** Determine whether the strand is running in the current thread.
173 :
174 : @return true if the current thread is executing a coroutine
175 : within this strand's dispatch loop.
176 : */
177 : bool
178 1 : running_in_this_thread() const noexcept
179 : {
180 1 : return detail::strand_service::running_in_this_thread(*impl_);
181 : }
182 :
183 : /** Compare two strands for equality.
184 :
185 : Two strands are equal if they share the same internal
186 : serialization state. Equal strands serialize coroutines
187 : with respect to each other.
188 :
189 : @param other The strand to compare against.
190 : @return true if both strands share the same implementation.
191 : */
192 : bool
193 3 : operator==(strand const& other) const noexcept
194 : {
195 3 : return impl_ == other.impl_;
196 : }
197 :
198 : /** Post a coroutine to the strand.
199 :
200 : The coroutine is always queued for execution, never resumed
201 : immediately. When the strand becomes available, queued
202 : coroutines execute in FIFO order on the underlying executor.
203 :
204 : @par Ordering
205 : Guarantees strict FIFO ordering relative to other post() calls.
206 : Use this instead of dispatch() when ordering matters.
207 :
208 : @param h The coroutine handle to post.
209 : */
210 : void
211 262 : post(any_coro h) const
212 : {
213 262 : detail::strand_service::post(*impl_, any_executor_ref(ex_), h);
214 262 : }
215 :
216 : /** Dispatch a coroutine through the strand.
217 :
218 : If the calling thread is already executing within this strand,
219 : the coroutine is resumed immediately via symmetric transfer,
220 : bypassing the queue. This provides optimal performance but
221 : means the coroutine may execute before previously queued work.
222 :
223 : Otherwise, the coroutine is queued and will execute in FIFO
224 : order relative to other queued coroutines.
225 :
226 : @par Ordering
227 : Callers requiring strict FIFO ordering should use post()
228 : instead, which always queues the coroutine.
229 :
230 : @param h The coroutine handle to dispatch.
231 : @return A coroutine handle for symmetric transfer.
232 : */
233 : any_coro
234 3 : dispatch(any_coro h) const
235 : {
236 3 : return detail::strand_service::dispatch(*impl_, any_executor_ref(ex_), h);
237 : }
238 : };
239 :
240 : // Deduction guide
241 : template<typename Ex>
242 : strand(Ex) -> strand<Ex>;
243 :
244 : } // namespace capy
245 : } // namespace boost
246 :
247 : #endif
|