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  

21  

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

23  

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

28  

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

33  

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

36  

37  
    @par Implementation
37  
    @par Implementation
38  
    The strand uses a service-based architecture with a fixed pool
38  
    The strand uses a service-based architecture with a fixed pool
39  
    of 211 implementation objects. New strands hash to select an
39  
    of 211 implementation objects. New strands hash to select an
40  
    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
41  
    serialization, which is harmless (just extra serialization)
41  
    serialization, which is harmless (just extra serialization)
42  
    and rare with 211 buckets.
42  
    and rare with 211 buckets.
43  

43  

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

50  

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

54  

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

59  

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

65  

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

68  

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

76  

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

81  

82  
    /** Construct a strand for the specified executor.
82  
    /** Construct a strand for the specified executor.
83  

83  

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

87  

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

90  

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

106  

107  
    /** Construct a copy.
107  
    /** Construct a copy.
108  

108  

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

114  

115  
    /** Construct by moving.
115  
    /** Construct by moving.
116  

116  

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

121  

122  
    /** Assign by copying.
122  
    /** Assign by copying.
123  
    */
123  
    */
124  
    strand& operator=(strand const&) = default;
124  
    strand& operator=(strand const&) = default;
125  

125  

126  
    /** Assign by moving.
126  
    /** Assign by moving.
127  

127  

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

132  

133  
    /** Return the underlying executor.
133  
    /** Return the underlying executor.
134  

134  

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

142  

143  
    /** Return the underlying execution context.
143  
    /** Return the underlying execution context.
144  

144  

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

153  

154  
    /** Notify that work has started.
154  
    /** Notify that work has started.
155  

155  

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

164  

165  
    /** Notify that work has finished.
165  
    /** Notify that work has finished.
166  

166  

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

175  

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

177  

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

186  

187  
    /** Compare two strands for equality.
187  
    /** Compare two strands for equality.
188  

188  

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

192  

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

201  

202  
    /** Post a coroutine to the strand.
202  
    /** Post a coroutine to the strand.
203  

203  

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

207  

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

211  

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

219  

220  
    /** Dispatch a coroutine through the strand.
220  
    /** Dispatch a coroutine through the strand.
221  

221  

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

226  

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

230  

231  
        @param h The coroutine handle to dispatch.
231  
        @param h The coroutine handle to dispatch.
232  

232  

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

241  

242  
// Deduction guide
242  
// Deduction guide
243  
template<typename Ex>
243  
template<typename Ex>
244  
strand(Ex) -> strand<Ex>;
244  
strand(Ex) -> strand<Ex>;
245  

245  

246  
} // namespace capy
246  
} // namespace capy
247  
} // namespace boost
247  
} // namespace boost
248  

248  

249  
#endif
249  
#endif