1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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 <coroutine>
14  
#include <coroutine>
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 -
//----------------------------------------------------------
 
22 -

 
23  

21  

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

23  

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

28  

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

33  

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

36  

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

43  

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

50  

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

54  

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

59  

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

65  

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

68  

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

76  

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

81  

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

83  

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

87  

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

90  

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

106  

109 -
    /** Copy constructor.
107 +
    /** Construct a copy.
110  

108  

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

114  

117 -
    /** Move constructor.
115 +
    /** Construct by moving.
 
116 +

 
117 +
        @note A moved-from strand is only safe to destroy
 
118 +
            or reassign.
118  
    */
119  
    */
119  
    strand(strand&&) = default;
120  
    strand(strand&&) = default;
120  

121  

121 -
    /** Copy assignment operator.
122 +
    /** Assign by copying.
122  
    */
123  
    */
123  
    strand& operator=(strand const&) = default;
124  
    strand& operator=(strand const&) = default;
124  

125  

125 -
    /** Move assignment operator.
126 +
    /** Assign by moving.
 
127 +

 
128 +
        @note A moved-from strand is only safe to destroy
 
129 +
            or reassign.
126  
    */
130  
    */
127  
    strand& operator=(strand&&) = default;
131  
    strand& operator=(strand&&) = default;
128  

132  

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

134  

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

142  

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

144  

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

153  

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

155  

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

164  

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

166  

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

175  

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

177  

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

186  

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

188  

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

192  

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

201  

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

203  

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

207  

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

211  

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

219  

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

221  

218  
        Returns a handle for symmetric transfer. If the calling
222  
        Returns a handle for symmetric transfer. If the calling
219  
        thread is already executing within this strand, returns `h`.
223  
        thread is already executing within this strand, returns `h`.
220  
        Otherwise, the coroutine is queued and
224  
        Otherwise, the coroutine is queued and
221  
        `std::noop_coroutine()` is returned.
225  
        `std::noop_coroutine()` is returned.
222  

226  

223  
        @par Ordering
227  
        @par Ordering
224  
        Callers requiring strict FIFO ordering should use post()
228  
        Callers requiring strict FIFO ordering should use post()
225  
        instead, which always queues the coroutine.
229  
        instead, which always queues the coroutine.
226  

230  

227  
        @param h The coroutine handle to dispatch.
231  
        @param h The coroutine handle to dispatch.
228  

232  

229  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
233  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
230  
    */
234  
    */
231  
    std::coroutine_handle<>
235  
    std::coroutine_handle<>
232  
    dispatch(std::coroutine_handle<> h) const
236  
    dispatch(std::coroutine_handle<> h) const
233  
    {
237  
    {
234  
        return detail::strand_service::dispatch(*impl_, executor_ref(ex_), h);
238  
        return detail::strand_service::dispatch(*impl_, executor_ref(ex_), h);
235  
    }
239  
    }
236  
};
240  
};
237  

241  

238  
// Deduction guide
242  
// Deduction guide
239  
template<typename Ex>
243  
template<typename Ex>
240  
strand(Ex) -> strand<Ex>;
244  
strand(Ex) -> strand<Ex>;
241  

245  

242  
} // namespace capy
246  
} // namespace capy
243  
} // namespace boost
247  
} // namespace boost
244  

248  

245  
#endif
249  
#endif