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 : - `operator()(h)` - May run immediately if strand is idle
51 : - `post(h)` - Always queues for later execution
52 : - `defer(h)` - Same as post (continuation hint)
53 :
54 : @par Thread Safety
55 : Distinct objects: Safe.
56 : Shared objects: Safe.
57 :
58 : @par Example
59 : @code
60 : thread_pool pool(4);
61 : auto strand = make_strand(pool.get_executor());
62 :
63 : // These coroutines will never run concurrently
64 : strand.post(coro1);
65 : strand.post(coro2);
66 : strand.post(coro3);
67 : @endcode
68 :
69 : @tparam Executor The type of the underlying executor. Must
70 : satisfy the `executor` concept.
71 :
72 : @see make_strand, executor
73 : */
74 : template<typename Executor>
75 : class strand
76 : {
77 : detail::strand_impl* impl_;
78 : post_dispatcher<Executor> post_;
79 :
80 : public:
81 : /** The type of the underlying executor.
82 : */
83 : using inner_executor_type = Executor;
84 :
85 : /** Construct a strand for the specified executor.
86 :
87 : Obtains a strand implementation from the service associated
88 : with the executor's context. The implementation is selected
89 : from a fixed pool using a hash function.
90 :
91 : @param ex The inner executor to wrap. Coroutines will
92 : ultimately be dispatched through this executor.
93 :
94 : @note This constructor is disabled if the argument is a
95 : strand type, to prevent strand-of-strand wrapping.
96 : */
97 : template<typename Executor1,
98 : typename = std::enable_if_t<
99 : !std::is_same_v<std::decay_t<Executor1>, strand> &&
100 : !detail::is_strand<std::decay_t<Executor1>>::value &&
101 : std::is_convertible_v<Executor1, Executor>>>
102 : explicit
103 24 : strand(Executor1&& ex)
104 24 : : impl_(detail::get_strand_service(ex.context())
105 24 : .get_implementation())
106 24 : , post_(std::forward<Executor1>(ex))
107 : {
108 24 : }
109 :
110 : /** Copy constructor.
111 :
112 : Creates a strand that shares serialization state with
113 : the original. Coroutines dispatched through either strand
114 : will be serialized with respect to each other.
115 : */
116 : strand(strand const&) = default;
117 :
118 : /** Move constructor.
119 : */
120 : strand(strand&&) = default;
121 :
122 : /** Copy assignment operator.
123 : */
124 : strand& operator=(strand const&) = default;
125 :
126 : /** Move assignment operator.
127 : */
128 : strand& operator=(strand&&) = default;
129 :
130 : /** Return the underlying executor.
131 :
132 : @return A const reference to the inner executor.
133 : */
134 : Executor const&
135 1 : get_inner_executor() const noexcept
136 : {
137 1 : return post_.get_inner_executor();
138 : }
139 :
140 : /** Return the underlying execution context.
141 :
142 : @return A reference to the execution context associated
143 : with the inner executor.
144 : */
145 : auto&
146 1 : context() const noexcept
147 : {
148 1 : return post_.get_inner_executor().context();
149 : }
150 :
151 : /** Notify that work has started.
152 :
153 : Delegates to the inner executor's `on_work_started()`.
154 : This is a no-op for most executor types.
155 : */
156 : void
157 2 : on_work_started() const noexcept
158 : {
159 2 : post_.get_inner_executor().on_work_started();
160 2 : }
161 :
162 : /** Notify that work has finished.
163 :
164 : Delegates to the inner executor's `on_work_finished()`.
165 : This is a no-op for most executor types.
166 : */
167 : void
168 2 : on_work_finished() const noexcept
169 : {
170 2 : post_.get_inner_executor().on_work_finished();
171 2 : }
172 :
173 : /** Determine whether the strand is running in the current thread.
174 :
175 : @return true if the current thread is executing a coroutine
176 : within this strand's dispatch loop.
177 : */
178 : bool
179 1 : running_in_this_thread() const noexcept
180 : {
181 1 : return detail::strand_service::running_in_this_thread(*impl_);
182 : }
183 :
184 : /** Compare two strands for equality.
185 :
186 : Two strands are equal if they share the same internal
187 : serialization state. Equal strands serialize coroutines
188 : with respect to each other.
189 :
190 : @param other The strand to compare against.
191 : @return true if both strands share the same implementation.
192 : */
193 : bool
194 3 : operator==(strand const& other) const noexcept
195 : {
196 3 : return impl_ == other.impl_;
197 : }
198 :
199 : /** Post a coroutine to the strand.
200 :
201 : The coroutine is always queued for execution, never resumed
202 : immediately. When the strand becomes available, queued
203 : coroutines execute in FIFO order on the underlying executor.
204 :
205 : @par Ordering
206 : Guarantees strict FIFO ordering relative to other post() calls.
207 : Use this instead of dispatch() when ordering matters.
208 :
209 : @param h The coroutine handle to post.
210 : */
211 : void
212 263 : post(any_coro h) const
213 : {
214 263 : detail::strand_service::post(*impl_, any_dispatcher(post_), h);
215 263 : }
216 :
217 : /** Defer a coroutine to the strand.
218 :
219 : Equivalent to `post()`. The defer hint indicates that the
220 : coroutine is a continuation of the current execution context,
221 : but strands treat this the same as post.
222 :
223 : @param h The coroutine handle to defer.
224 : */
225 : void
226 1 : defer(any_coro h) const
227 : {
228 1 : post(h);
229 1 : }
230 :
231 : /** Dispatch a coroutine through the strand.
232 :
233 : If the calling thread is already executing within this strand,
234 : the coroutine is resumed immediately via symmetric transfer,
235 : bypassing the queue. This provides optimal performance but
236 : means the coroutine may execute before previously queued work.
237 :
238 : Otherwise, the coroutine is queued and will execute in FIFO
239 : order relative to other queued coroutines.
240 :
241 : @par Ordering
242 : Callers requiring strict FIFO ordering should use post()
243 : instead, which always queues the coroutine.
244 :
245 : @param h The coroutine handle to dispatch.
246 : @return A coroutine handle for symmetric transfer.
247 : */
248 : // TODO: measure before deciding to split strand_impl for inlining fast-path check
249 : any_coro
250 3 : operator()(any_coro h) const
251 : {
252 3 : return detail::strand_service::dispatch(*impl_, any_dispatcher(post_), h);
253 : }
254 : };
255 :
256 : // Deduction guide
257 : template<typename Executor>
258 : strand(Executor) -> strand<Executor>;
259 :
260 : } // namespace capy
261 : } // namespace boost
262 :
263 : #endif
|