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 <boost/capy/continuation.hpp>
14  
#include <boost/capy/continuation.hpp>
15  
#include <coroutine>
15  
#include <coroutine>
16  
#include <boost/capy/ex/detail/strand_service.hpp>
16  
#include <boost/capy/ex/detail/strand_service.hpp>
17  

17  

18  
#include <type_traits>
18  
#include <type_traits>
19  

19  

20  
namespace boost {
20  
namespace boost {
21  
namespace capy {
21  
namespace capy {
22  

22  

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

24  

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

29  

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

34  

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

37  

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

44  

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

51  

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

55  

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

60  

61  
    // These continuations will never run concurrently
61  
    // These continuations will never run concurrently
62  
    continuation c1{h1}, c2{h2}, c3{h3};
62  
    continuation c1{h1}, c2{h2}, c3{h3};
63  
    strand.post(c1);
63  
    strand.post(c1);
64  
    strand.post(c2);
64  
    strand.post(c2);
65  
    strand.post(c3);
65  
    strand.post(c3);
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  
    /** Construct a copy.
109  
    /** Construct a copy.
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  
    /** Construct by moving.
117  
    /** Construct by moving.
118  

118  

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

123  

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

127  

128  
    /** Assign by moving.
128  
    /** Assign by moving.
129  

129  

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

134  

135  
    /** Return the underlying executor.
135  
    /** Return the underlying executor.
136  

136  

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

144  

145  
    /** Return the underlying execution context.
145  
    /** Return the underlying execution context.
146  

146  

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

155  

156  
    /** Notify that work has started.
156  
    /** Notify that work has started.
157  

157  

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

166  

167  
    /** Notify that work has finished.
167  
    /** Notify that work has finished.
168  

168  

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

177  

178  
    /** Determine whether the strand is running in the current thread.
178  
    /** Determine whether the strand is running in the current thread.
179  

179  

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

188  

189  
    /** Compare two strands for equality.
189  
    /** Compare two strands for equality.
190  

190  

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

194  

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

203  

204  
    /** Post a continuation to the strand.
204  
    /** Post a continuation to the strand.
205  

205  

206  
        The continuation is always queued for execution, never resumed
206  
        The continuation is always queued for execution, never resumed
207  
        immediately. When the strand becomes available, queued
207  
        immediately. When the strand becomes available, queued
208  
        work executes in FIFO order on the underlying executor.
208  
        work executes in FIFO order on the underlying executor.
209  

209  

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

213  

214  
        @param c The continuation to post. The caller retains
214  
        @param c The continuation to post. The caller retains
215  
            ownership; the continuation must remain valid until
215  
            ownership; the continuation must remain valid until
216  
            it is dequeued and resumed.
216  
            it is dequeued and resumed.
217  
    */
217  
    */
218  
    void
218  
    void
219  
    post(continuation& c) const
219  
    post(continuation& c) const
220  
    {
220  
    {
221  
        detail::strand_service::post(*impl_, executor_ref(ex_), c.h);
221  
        detail::strand_service::post(*impl_, executor_ref(ex_), c.h);
222  
    }
222  
    }
223  

223  

224  
    /** Dispatch a continuation through the strand.
224  
    /** Dispatch a continuation through the strand.
225  

225  

226  
        Returns a handle for symmetric transfer. If the calling
226  
        Returns a handle for symmetric transfer. If the calling
227  
        thread is already executing within this strand, returns `c.h`.
227  
        thread is already executing within this strand, returns `c.h`.
228  
        Otherwise, the continuation is queued and
228  
        Otherwise, the continuation is queued and
229  
        `std::noop_coroutine()` is returned.
229  
        `std::noop_coroutine()` is returned.
230  

230  

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

234  

235  
        @param c The continuation to dispatch. The caller retains
235  
        @param c The continuation to dispatch. The caller retains
236  
            ownership; the continuation must remain valid until
236  
            ownership; the continuation must remain valid until
237  
            it is dequeued and resumed.
237  
            it is dequeued and resumed.
238  

238  

239  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
239  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
240  
    */
240  
    */
241  
    std::coroutine_handle<>
241  
    std::coroutine_handle<>
242  
    dispatch(continuation& c) const
242  
    dispatch(continuation& c) const
243  
    {
243  
    {
244  
        return detail::strand_service::dispatch(*impl_, executor_ref(ex_), c.h);
244  
        return detail::strand_service::dispatch(*impl_, executor_ref(ex_), c.h);
245  
    }
245  
    }
246  
};
246  
};
247  

247  

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

251  

252  
} // namespace capy
252  
} // namespace capy
253  
} // namespace boost
253  
} // namespace boost
254  

254  

255  
#endif
255  
#endif