1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_EX_STRAND_HPP
10  
#ifndef BOOST_CAPY_EX_STRAND_HPP
11  
#define BOOST_CAPY_EX_STRAND_HPP
11  
#define BOOST_CAPY_EX_STRAND_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/coro.hpp>
14  
#include <boost/capy/coro.hpp>
15  
#include <boost/capy/ex/detail/strand_service.hpp>
15  
#include <boost/capy/ex/detail/strand_service.hpp>
16  

16  

17  
#include <type_traits>
17  
#include <type_traits>
18  

18  

19  
namespace boost {
19  
namespace boost {
20  
namespace capy {
20  
namespace capy {
21  

21  

22  
//----------------------------------------------------------
22  
//----------------------------------------------------------
23  

23  

24  
/** Provides serialized coroutine execution for any executor type.
24  
/** Provides serialized coroutine execution for any executor type.
25  

25  

26  
    A strand wraps an inner executor and ensures that coroutines
26  
    A strand wraps an inner executor and ensures that coroutines
27  
    dispatched through it never run concurrently. At most one
27  
    dispatched through it never run concurrently. At most one
28  
    coroutine executes at a time within a strand, even when the
28  
    coroutine executes at a time within a strand, even when the
29  
    underlying executor runs on multiple threads.
29  
    underlying executor runs on multiple threads.
30  

30  

31  
    Strands are lightweight handles that can be copied freely.
31  
    Strands are lightweight handles that can be copied freely.
32  
    Copies share the same internal serialization state, so
32  
    Copies share the same internal serialization state, so
33  
    coroutines dispatched through any copy are serialized with
33  
    coroutines dispatched through any copy are serialized with
34  
    respect to all other copies.
34  
    respect to all other copies.
35  

35  

36  
    @par Invariant
36  
    @par Invariant
37  
    Coroutines resumed through a strand shall not run concurrently.
37  
    Coroutines resumed through a strand shall not run concurrently.
38  

38  

39  
    @par Implementation
39  
    @par Implementation
40  
    The strand uses a service-based architecture with a fixed pool
40  
    The strand uses a service-based architecture with a fixed pool
41  
    of 211 implementation objects. New strands hash to select an
41  
    of 211 implementation objects. New strands hash to select an
42  
    impl from the pool. Strands that hash to the same index share
42  
    impl from the pool. Strands that hash to the same index share
43  
    serialization, which is harmless (just extra serialization)
43  
    serialization, which is harmless (just extra serialization)
44  
    and rare with 211 buckets.
44  
    and rare with 211 buckets.
45  

45  

46  
    @par Executor Concept
46  
    @par Executor Concept
47  
    This class satisfies the `Executor` concept, providing:
47  
    This class satisfies the `Executor` concept, providing:
48  
    - `context()` - Returns the underlying execution context
48  
    - `context()` - Returns the underlying execution context
49  
    - `on_work_started()` / `on_work_finished()` - Work tracking
49  
    - `on_work_started()` / `on_work_finished()` - Work tracking
50  
    - `dispatch(h)` - May run immediately if strand is idle
50  
    - `dispatch(h)` - May run immediately if strand is idle
51  
    - `post(h)` - Always queues for later execution
51  
    - `post(h)` - Always queues for later execution
52  

52  

53  
    @par Thread Safety
53  
    @par Thread Safety
54  
    Distinct objects: Safe.
54  
    Distinct objects: Safe.
55  
    Shared objects: Safe.
55  
    Shared objects: Safe.
56  

56  

57  
    @par Example
57  
    @par Example
58  
    @code
58  
    @code
59  
    thread_pool pool(4);
59  
    thread_pool pool(4);
60  
    auto strand = make_strand(pool.get_executor());
60  
    auto strand = make_strand(pool.get_executor());
61  

61  

62  
    // These coroutines will never run concurrently
62  
    // These coroutines will never run concurrently
63  
    strand.post(coro1);
63  
    strand.post(coro1);
64  
    strand.post(coro2);
64  
    strand.post(coro2);
65  
    strand.post(coro3);
65  
    strand.post(coro3);
66  
    @endcode
66  
    @endcode
67  

67  

68  
    @tparam E The type of the underlying executor. Must
68  
    @tparam E The type of the underlying executor. Must
69  
        satisfy the `Executor` concept.
69  
        satisfy the `Executor` concept.
70  

70  

71  
    @see make_strand, Executor
71  
    @see make_strand, Executor
72  
*/
72  
*/
73  
template<typename Ex>
73  
template<typename Ex>
74  
class strand
74  
class strand
75  
{
75  
{
76  
    detail::strand_impl* impl_;
76  
    detail::strand_impl* impl_;
77  
    Ex ex_;
77  
    Ex ex_;
78  

78  

79  
public:
79  
public:
80  
    /** The type of the underlying executor.
80  
    /** The type of the underlying executor.
81  
    */
81  
    */
82  
    using inner_executor_type = Ex;
82  
    using inner_executor_type = Ex;
83  

83  

84  
    /** Construct a strand for the specified executor.
84  
    /** Construct a strand for the specified executor.
85  

85  

86  
        Obtains a strand implementation from the service associated
86  
        Obtains a strand implementation from the service associated
87  
        with the executor's context. The implementation is selected
87  
        with the executor's context. The implementation is selected
88  
        from a fixed pool using a hash function.
88  
        from a fixed pool using a hash function.
89  

89  

90  
        @param ex The inner executor to wrap. Coroutines will
90  
        @param ex The inner executor to wrap. Coroutines will
91  
            ultimately be dispatched through this executor.
91  
            ultimately be dispatched through this executor.
92  

92  

93  
        @note This constructor is disabled if the argument is a
93  
        @note This constructor is disabled if the argument is a
94  
            strand type, to prevent strand-of-strand wrapping.
94  
            strand type, to prevent strand-of-strand wrapping.
95  
    */
95  
    */
96  
    template<typename Ex1,
96  
    template<typename Ex1,
97  
        typename = std::enable_if_t<
97  
        typename = std::enable_if_t<
98  
            !std::is_same_v<std::decay_t<Ex1>, strand> &&
98  
            !std::is_same_v<std::decay_t<Ex1>, strand> &&
99  
            !detail::is_strand<std::decay_t<Ex1>>::value &&
99  
            !detail::is_strand<std::decay_t<Ex1>>::value &&
100  
            std::is_convertible_v<Ex1, Ex>>>
100  
            std::is_convertible_v<Ex1, Ex>>>
101  
    explicit
101  
    explicit
102  
    strand(Ex1&& ex)
102  
    strand(Ex1&& ex)
103  
        : impl_(detail::get_strand_service(ex.context())
103  
        : impl_(detail::get_strand_service(ex.context())
104  
            .get_implementation())
104  
            .get_implementation())
105  
        , ex_(std::forward<Ex1>(ex))
105  
        , ex_(std::forward<Ex1>(ex))
106  
    {
106  
    {
107  
    }
107  
    }
108  

108  

109  
    /** Copy constructor.
109  
    /** Copy constructor.
110  

110  

111  
        Creates a strand that shares serialization state with
111  
        Creates a strand that shares serialization state with
112  
        the original. Coroutines dispatched through either strand
112  
        the original. Coroutines dispatched through either strand
113  
        will be serialized with respect to each other.
113  
        will be serialized with respect to each other.
114  
    */
114  
    */
115  
    strand(strand const&) = default;
115  
    strand(strand const&) = default;
116  

116  

117  
    /** Move constructor.
117  
    /** Move constructor.
118  
    */
118  
    */
119  
    strand(strand&&) = default;
119  
    strand(strand&&) = default;
120  

120  

121  
    /** Copy assignment operator.
121  
    /** Copy assignment operator.
122  
    */
122  
    */
123  
    strand& operator=(strand const&) = default;
123  
    strand& operator=(strand const&) = default;
124  

124  

125  
    /** Move assignment operator.
125  
    /** Move assignment operator.
126  
    */
126  
    */
127  
    strand& operator=(strand&&) = default;
127  
    strand& operator=(strand&&) = default;
128  

128  

129  
    /** Return the underlying executor.
129  
    /** Return the underlying executor.
130  

130  

131  
        @return A const reference to the inner executor.
131  
        @return A const reference to the inner executor.
132  
    */
132  
    */
133  
    Ex const&
133  
    Ex const&
134  
    get_inner_executor() const noexcept
134  
    get_inner_executor() const noexcept
135  
    {
135  
    {
136  
        return ex_;
136  
        return ex_;
137  
    }
137  
    }
138  

138  

139  
    /** Return the underlying execution context.
139  
    /** Return the underlying execution context.
140  

140  

141  
        @return A reference to the execution context associated
141  
        @return A reference to the execution context associated
142  
            with the inner executor.
142  
            with the inner executor.
143  
    */
143  
    */
144  
    auto&
144  
    auto&
145  
    context() const noexcept
145  
    context() const noexcept
146  
    {
146  
    {
147  
        return ex_.context();
147  
        return ex_.context();
148  
    }
148  
    }
149  

149  

150  
    /** Notify that work has started.
150  
    /** Notify that work has started.
151  

151  

152  
        Delegates to the inner executor's `on_work_started()`.
152  
        Delegates to the inner executor's `on_work_started()`.
153  
        This is a no-op for most executor types.
153  
        This is a no-op for most executor types.
154  
    */
154  
    */
155  
    void
155  
    void
156  
    on_work_started() const noexcept
156  
    on_work_started() const noexcept
157  
    {
157  
    {
158  
        ex_.on_work_started();
158  
        ex_.on_work_started();
159  
    }
159  
    }
160  

160  

161  
    /** Notify that work has finished.
161  
    /** Notify that work has finished.
162  

162  

163  
        Delegates to the inner executor's `on_work_finished()`.
163  
        Delegates to the inner executor's `on_work_finished()`.
164  
        This is a no-op for most executor types.
164  
        This is a no-op for most executor types.
165  
    */
165  
    */
166  
    void
166  
    void
167  
    on_work_finished() const noexcept
167  
    on_work_finished() const noexcept
168  
    {
168  
    {
169  
        ex_.on_work_finished();
169  
        ex_.on_work_finished();
170  
    }
170  
    }
171  

171  

172  
    /** Determine whether the strand is running in the current thread.
172  
    /** Determine whether the strand is running in the current thread.
173  

173  

174  
        @return true if the current thread is executing a coroutine
174  
        @return true if the current thread is executing a coroutine
175  
            within this strand's dispatch loop.
175  
            within this strand's dispatch loop.
176  
    */
176  
    */
177  
    bool
177  
    bool
178  
    running_in_this_thread() const noexcept
178  
    running_in_this_thread() const noexcept
179  
    {
179  
    {
180  
        return detail::strand_service::running_in_this_thread(*impl_);
180  
        return detail::strand_service::running_in_this_thread(*impl_);
181  
    }
181  
    }
182  

182  

183  
    /** Compare two strands for equality.
183  
    /** Compare two strands for equality.
184  

184  

185  
        Two strands are equal if they share the same internal
185  
        Two strands are equal if they share the same internal
186  
        serialization state. Equal strands serialize coroutines
186  
        serialization state. Equal strands serialize coroutines
187  
        with respect to each other.
187  
        with respect to each other.
188  

188  

189  
        @param other The strand to compare against.
189  
        @param other The strand to compare against.
190  
        @return true if both strands share the same implementation.
190  
        @return true if both strands share the same implementation.
191  
    */
191  
    */
192  
    bool
192  
    bool
193  
    operator==(strand const& other) const noexcept
193  
    operator==(strand const& other) const noexcept
194  
    {
194  
    {
195  
        return impl_ == other.impl_;
195  
        return impl_ == other.impl_;
196  
    }
196  
    }
197  

197  

198  
    /** Post a coroutine to the strand.
198  
    /** Post a coroutine to the strand.
199  

199  

200  
        The coroutine is always queued for execution, never resumed
200  
        The coroutine is always queued for execution, never resumed
201  
        immediately. When the strand becomes available, queued
201  
        immediately. When the strand becomes available, queued
202  
        coroutines execute in FIFO order on the underlying executor.
202  
        coroutines execute in FIFO order on the underlying executor.
203  

203  

204  
        @par Ordering
204  
        @par Ordering
205  
        Guarantees strict FIFO ordering relative to other post() calls.
205  
        Guarantees strict FIFO ordering relative to other post() calls.
206  
        Use this instead of dispatch() when ordering matters.
206  
        Use this instead of dispatch() when ordering matters.
207  

207  

208  
        @param h The coroutine handle to post.
208  
        @param h The coroutine handle to post.
209  
    */
209  
    */
210  
    void
210  
    void
211  
    post(coro h) const
211  
    post(coro h) const
212  
    {
212  
    {
213  
        detail::strand_service::post(*impl_, executor_ref(ex_), h);
213  
        detail::strand_service::post(*impl_, executor_ref(ex_), h);
214  
    }
214  
    }
215  

215  

216  
    /** Dispatch a coroutine through the strand.
216  
    /** Dispatch a coroutine through the strand.
217  

217  

218  
        If the calling thread is already executing within this strand,
218  
        If the calling thread is already executing within this strand,
219  
        the coroutine is resumed immediately by calling `h.resume()`.
219  
        the coroutine is resumed immediately by calling `h.resume()`.
220  
        The call returns when the coroutine suspends or completes.
220  
        The call returns when the coroutine suspends or completes.
221  
        This provides optimal performance but means the coroutine may
221  
        This provides optimal performance but means the coroutine may
222  
        execute before previously queued work.
222  
        execute before previously queued work.
223  

223  

224  
        Otherwise, the coroutine is queued and will execute in FIFO
224  
        Otherwise, the coroutine is queued and will execute in FIFO
225  
        order relative to other queued coroutines.
225  
        order relative to other queued coroutines.
226  

226  

227  
        After this function returns, the state of `h` is unspecified.
227  
        After this function returns, the state of `h` is unspecified.
228  
        The coroutine may have completed, been destroyed, or suspended
228  
        The coroutine may have completed, been destroyed, or suspended
229  
        at a different suspension point. Callers must not assume `h`
229  
        at a different suspension point. Callers must not assume `h`
230  
        remains valid after calling `dispatch`.
230  
        remains valid after calling `dispatch`.
231  

231  

232  
        @par Ordering
232  
        @par Ordering
233  
        Callers requiring strict FIFO ordering should use post()
233  
        Callers requiring strict FIFO ordering should use post()
234  
        instead, which always queues the coroutine.
234  
        instead, which always queues the coroutine.
235  

235  

236  
        @note Because this function may call `h.resume()` before
236  
        @note Because this function may call `h.resume()` before
237  
        returning, it cannot be used to implement symmetric transfer
237  
        returning, it cannot be used to implement symmetric transfer
238  
        from `await_suspend`.
238  
        from `await_suspend`.
239  

239  

240  
        @param h The coroutine handle to dispatch.
240  
        @param h The coroutine handle to dispatch.
241  
    */
241  
    */
242  
    void
242  
    void
243  
    dispatch(coro h) const
243  
    dispatch(coro h) const
244  
    {
244  
    {
245  
        detail::strand_service::dispatch(*impl_, executor_ref(ex_), h);
245  
        detail::strand_service::dispatch(*impl_, executor_ref(ex_), h);
246  
    }
246  
    }
247  
};
247  
};
248  

248  

249  
// Deduction guide
249  
// Deduction guide
250  
template<typename Ex>
250  
template<typename Ex>
251  
strand(Ex) -> strand<Ex>;
251  
strand(Ex) -> strand<Ex>;
252  

252  

253  
} // namespace capy
253  
} // namespace capy
254  
} // namespace boost
254  
} // namespace boost
255  

255  

256  
#endif
256  
#endif