TLA Line data 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 : The strand uses a service-based architecture with a fixed pool
40 : of 211 implementation objects. New strands hash to select an
41 : impl from the pool. Strands that hash to the same index share
42 : serialization, which is harmless (just extra serialization)
43 : and rare with 211 buckets.
44 :
45 : @par Executor Concept
46 : This class satisfies the `Executor` concept, providing:
47 : - `context()` - Returns the underlying execution context
48 : - `on_work_started()` / `on_work_finished()` - Work tracking
49 : - `dispatch(continuation&)` - May run immediately if strand is idle
50 : - `post(continuation&)` - Always queues for later execution
51 :
52 : @par Thread Safety
53 : Distinct objects: Safe.
54 : Shared objects: Safe.
55 :
56 : @par Example
57 : @code
58 : thread_pool pool(4);
59 : auto strand = make_strand(pool.get_executor());
60 :
61 : // These continuations will never run concurrently
62 : continuation c1{h1}, c2{h2}, c3{h3};
63 : strand.post(c1);
64 : strand.post(c2);
65 : strand.post(c3);
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 HIT 27 : strand(Ex1&& ex)
103 27 : : impl_(detail::get_strand_service(ex.context())
104 27 : .get_implementation())
105 27 : , ex_(std::forward<Ex1>(ex))
106 : {
107 27 : }
108 :
109 : /** Construct a copy.
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 1 : strand(strand const&) = default;
116 :
117 : /** Construct by moving.
118 :
119 : @note A moved-from strand is only safe to destroy
120 : or reassign.
121 : */
122 : strand(strand&&) = default;
123 :
124 : /** Assign by copying.
125 : */
126 : strand& operator=(strand const&) = default;
127 :
128 : /** Assign by moving.
129 :
130 : @note A moved-from strand is only safe to destroy
131 : or reassign.
132 : */
133 : strand& operator=(strand&&) = default;
134 :
135 : /** Return the underlying executor.
136 :
137 : @return A const reference to the inner executor.
138 : */
139 : Ex const&
140 1 : get_inner_executor() const noexcept
141 : {
142 1 : return ex_;
143 : }
144 :
145 : /** Return the underlying execution context.
146 :
147 : @return A reference to the execution context associated
148 : with the inner executor.
149 : */
150 : auto&
151 4 : context() const noexcept
152 : {
153 4 : return ex_.context();
154 : }
155 :
156 : /** Notify that work has started.
157 :
158 : Delegates to the inner executor's `on_work_started()`.
159 : This is a no-op for most executor types.
160 : */
161 : void
162 5 : on_work_started() const noexcept
163 : {
164 5 : ex_.on_work_started();
165 5 : }
166 :
167 : /** Notify that work has finished.
168 :
169 : Delegates to the inner executor's `on_work_finished()`.
170 : This is a no-op for most executor types.
171 : */
172 : void
173 5 : on_work_finished() const noexcept
174 : {
175 5 : ex_.on_work_finished();
176 5 : }
177 :
178 : /** Determine whether the strand is running in the current thread.
179 :
180 : @return true if the current thread is executing a coroutine
181 : within this strand's dispatch loop.
182 : */
183 : bool
184 2 : running_in_this_thread() const noexcept
185 : {
186 2 : return detail::strand_service::running_in_this_thread(*impl_);
187 : }
188 :
189 : /** Compare two strands for equality.
190 :
191 : Two strands are equal if they share the same internal
192 : serialization state. Equal strands serialize coroutines
193 : with respect to each other.
194 :
195 : @param other The strand to compare against.
196 : @return true if both strands share the same implementation.
197 : */
198 : bool
199 4 : operator==(strand const& other) const noexcept
200 : {
201 4 : return impl_ == other.impl_;
202 : }
203 :
204 : /** Post a continuation to the strand.
205 :
206 : The continuation is always queued for execution, never resumed
207 : immediately. When the strand becomes available, queued
208 : work executes in FIFO order on the underlying executor.
209 :
210 : @par Ordering
211 : Guarantees strict FIFO ordering relative to other post() calls.
212 : Use this instead of dispatch() when ordering matters.
213 :
214 : @param c The continuation to post. The caller retains
215 : ownership; the continuation must remain valid until
216 : it is dequeued and resumed.
217 : */
218 : void
219 327 : post(continuation& c) const
220 : {
221 327 : detail::strand_service::post(*impl_, executor_ref(ex_), c.h);
222 327 : }
223 :
224 : /** Dispatch a continuation through the strand.
225 :
226 : Returns a handle for symmetric transfer. If the calling
227 : thread is already executing within this strand, returns `c.h`.
228 : Otherwise, the continuation is queued and
229 : `std::noop_coroutine()` is returned.
230 :
231 : @par Ordering
232 : Callers requiring strict FIFO ordering should use post()
233 : instead, which always queues the continuation.
234 :
235 : @param c The continuation to dispatch. The caller retains
236 : ownership; the continuation must remain valid until
237 : it is dequeued and resumed.
238 :
239 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
240 : */
241 : std::coroutine_handle<>
242 8 : dispatch(continuation& c) const
243 : {
244 8 : return detail::strand_service::dispatch(*impl_, executor_ref(ex_), c.h);
245 : }
246 : };
247 :
248 : // Deduction guide
249 : template<typename Ex>
250 : strand(Ex) -> strand<Ex>;
251 :
252 : } // namespace capy
253 : } // namespace boost
254 :
255 : #endif
|