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 +
//----------------------------------------------------------
 
23 +

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

25  

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

30  

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

35  

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

38  

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

45  

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

52  

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

56  

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

61  

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

67  

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

70  

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

78  

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

83  

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

85  

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

89  

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

92  

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

108  

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

110  

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

116  

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

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

120  

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

124  

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

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

128  

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

130  

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

138  

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

140  

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

149  

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

151  

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

160  

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

162  

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

171  

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

173  

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

182  

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

184  

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

188  

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

197  

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

199  

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

203  

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

207  

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

215  

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

217  

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

222  

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

226  

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

228  

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

237  

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

241  

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

244  

249  
#endif
245  
#endif