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 : 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 HIT 11442 : strand(Ex1&& ex)
107 11442 : : impl_(detail::get_strand_service(ex.context())
108 11442 : .create_implementation())
109 11442 : , ex_(std::forward<Ex1>(ex))
110 : {
111 11442 : }
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 9 : 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 11443 : strand(strand&&) = default;
127 :
128 : /** Assign by copying.
129 : */
130 1 : 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 1 : 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 1 : get_inner_executor() const noexcept
145 : {
146 1 : 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 5 : context() const noexcept
156 : {
157 5 : 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 6 : on_work_started() const noexcept
167 : {
168 6 : ex_.on_work_started();
169 6 : }
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 6 : on_work_finished() const noexcept
178 : {
179 6 : ex_.on_work_finished();
180 6 : }
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 4 : running_in_this_thread() const noexcept
189 : {
190 4 : 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 499505 : operator==(strand const& other) const noexcept
204 : {
205 499505 : 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 30335 : post(continuation& c) const
224 : {
225 30335 : detail::strand_service::post(impl_, executor_ref(ex_), c);
226 30335 : }
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 8 : dispatch(continuation& c) const
247 : {
248 8 : 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
|