1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
// Copyright (c) 2026 Steve Gerbino
3  
// Copyright (c) 2026 Steve Gerbino
4  
// Copyright (c) 2026 Michael Vandeberg
4  
// Copyright (c) 2026 Michael Vandeberg
5  
//
5  
//
6  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
7  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8  
//
8  
//
9  
// Official repository: https://github.com/cppalliance/corosio
9  
// Official repository: https://github.com/cppalliance/corosio
10  
//
10  
//
11  

11  

12  
#ifndef BOOST_COROSIO_IO_CONTEXT_HPP
12  
#ifndef BOOST_COROSIO_IO_CONTEXT_HPP
13  
#define BOOST_COROSIO_IO_CONTEXT_HPP
13  
#define BOOST_COROSIO_IO_CONTEXT_HPP
14  

14  

15  
#include <boost/corosio/detail/config.hpp>
15  
#include <boost/corosio/detail/config.hpp>
16  
#include <boost/corosio/detail/continuation_op.hpp>
16  
#include <boost/corosio/detail/continuation_op.hpp>
17  
#include <boost/corosio/detail/platform.hpp>
17  
#include <boost/corosio/detail/platform.hpp>
18  
#include <boost/corosio/detail/scheduler.hpp>
18  
#include <boost/corosio/detail/scheduler.hpp>
19  
#include <boost/capy/continuation.hpp>
19  
#include <boost/capy/continuation.hpp>
20  
#include <boost/capy/ex/execution_context.hpp>
20  
#include <boost/capy/ex/execution_context.hpp>
21  

21  

22  
#include <chrono>
22  
#include <chrono>
23  
#include <coroutine>
23  
#include <coroutine>
24  
#include <cstddef>
24  
#include <cstddef>
25  
#include <limits>
25  
#include <limits>
26  
#include <thread>
26  
#include <thread>
27  

27  

28  
namespace boost::corosio {
28  
namespace boost::corosio {
29  

29  

30  
namespace detail {
30  
namespace detail {
31  
struct timer_service_access;
31  
struct timer_service_access;
32  
} // namespace detail
32  
} // namespace detail
33  

33  

34  
/** An I/O context for running asynchronous operations.
34  
/** An I/O context for running asynchronous operations.
35  

35  

36  
    The io_context provides an execution environment for async
36  
    The io_context provides an execution environment for async
37  
    operations. It maintains a queue of pending work items and
37  
    operations. It maintains a queue of pending work items and
38  
    processes them when `run()` is called.
38  
    processes them when `run()` is called.
39  

39  

40  
    The default and unsigned constructors select the platform's
40  
    The default and unsigned constructors select the platform's
41  
    native backend:
41  
    native backend:
42  
    - Windows: IOCP
42  
    - Windows: IOCP
43  
    - Linux: epoll
43  
    - Linux: epoll
44  
    - BSD/macOS: kqueue
44  
    - BSD/macOS: kqueue
45  
    - Other POSIX: select
45  
    - Other POSIX: select
46  

46  

47  
    The template constructor accepts a backend tag value to
47  
    The template constructor accepts a backend tag value to
48  
    choose a specific backend at compile time:
48  
    choose a specific backend at compile time:
49  

49  

50  
    @par Example
50  
    @par Example
51  
    @code
51  
    @code
52  
    io_context ioc;                   // platform default
52  
    io_context ioc;                   // platform default
53  
    io_context ioc2(corosio::epoll);  // explicit backend
53  
    io_context ioc2(corosio::epoll);  // explicit backend
54  
    @endcode
54  
    @endcode
55  

55  

56  
    @par Thread Safety
56  
    @par Thread Safety
57  
    Distinct objects: Safe.@n
57  
    Distinct objects: Safe.@n
58  
    Shared objects: Safe, if using a concurrency hint greater
58  
    Shared objects: Safe, if using a concurrency hint greater
59  
    than 1.
59  
    than 1.
60  

60  

61  
    @see epoll_t, select_t, kqueue_t, iocp_t
61  
    @see epoll_t, select_t, kqueue_t, iocp_t
62  
*/
62  
*/
63  
class BOOST_COROSIO_DECL io_context : public capy::execution_context
63  
class BOOST_COROSIO_DECL io_context : public capy::execution_context
64  
{
64  
{
65  
    friend struct detail::timer_service_access;
65  
    friend struct detail::timer_service_access;
66  

66  

67  
protected:
67  
protected:
68  
    detail::scheduler* sched_;
68  
    detail::scheduler* sched_;
69  

69  

70  
public:
70  
public:
71  
    /** The executor type for this context. */
71  
    /** The executor type for this context. */
72  
    class executor_type;
72  
    class executor_type;
73  

73  

74  
    /** Construct with default concurrency and platform backend. */
74  
    /** Construct with default concurrency and platform backend. */
75  
    io_context();
75  
    io_context();
76  

76  

77  
    /** Construct with a concurrency hint and platform backend.
77  
    /** Construct with a concurrency hint and platform backend.
78  

78  

79  
        @param concurrency_hint Hint for the number of threads
79  
        @param concurrency_hint Hint for the number of threads
80  
            that will call `run()`.
80  
            that will call `run()`.
81  
    */
81  
    */
82  
    explicit io_context(unsigned concurrency_hint);
82  
    explicit io_context(unsigned concurrency_hint);
83  

83  

84  
    /** Construct with an explicit backend tag.
84  
    /** Construct with an explicit backend tag.
85  

85  

86  
        @param backend The backend tag value selecting the I/O
86  
        @param backend The backend tag value selecting the I/O
87  
            multiplexer (e.g. `corosio::epoll`).
87  
            multiplexer (e.g. `corosio::epoll`).
88  
        @param concurrency_hint Hint for the number of threads
88  
        @param concurrency_hint Hint for the number of threads
89  
            that will call `run()`.
89  
            that will call `run()`.
90  
    */
90  
    */
91  
    template<class Backend>
91  
    template<class Backend>
92  
        requires requires { Backend::construct; }
92  
        requires requires { Backend::construct; }
93  
    explicit io_context(
93  
    explicit io_context(
94  
        Backend backend,
94  
        Backend backend,
95  
        unsigned concurrency_hint = std::thread::hardware_concurrency())
95  
        unsigned concurrency_hint = std::thread::hardware_concurrency())
96  
        : capy::execution_context(this)
96  
        : capy::execution_context(this)
97  
        , sched_(nullptr)
97  
        , sched_(nullptr)
98  
    {
98  
    {
99  
        (void)backend;
99  
        (void)backend;
100  
        sched_ = &Backend::construct(*this, concurrency_hint);
100  
        sched_ = &Backend::construct(*this, concurrency_hint);
101  
    }
101  
    }
102  

102  

103  
    ~io_context();
103  
    ~io_context();
104  

104  

105  
    io_context(io_context const&)            = delete;
105  
    io_context(io_context const&)            = delete;
106  
    io_context& operator=(io_context const&) = delete;
106  
    io_context& operator=(io_context const&) = delete;
107  

107  

108  
    /** Return an executor for this context.
108  
    /** Return an executor for this context.
109  

109  

110  
        The returned executor can be used to dispatch coroutines
110  
        The returned executor can be used to dispatch coroutines
111  
        and post work items to this context.
111  
        and post work items to this context.
112  

112  

113  
        @return An executor associated with this context.
113  
        @return An executor associated with this context.
114  
    */
114  
    */
115  
    executor_type get_executor() const noexcept;
115  
    executor_type get_executor() const noexcept;
116  

116  

117  
    /** Signal the context to stop processing.
117  
    /** Signal the context to stop processing.
118  

118  

119  
        This causes `run()` to return as soon as possible. Any pending
119  
        This causes `run()` to return as soon as possible. Any pending
120  
        work items remain queued.
120  
        work items remain queued.
121  
    */
121  
    */
122  
    void stop()
122  
    void stop()
123  
    {
123  
    {
124  
        sched_->stop();
124  
        sched_->stop();
125  
    }
125  
    }
126  

126  

127  
    /** Return whether the context has been stopped.
127  
    /** Return whether the context has been stopped.
128  

128  

129  
        @return `true` if `stop()` has been called and `restart()`
129  
        @return `true` if `stop()` has been called and `restart()`
130  
            has not been called since.
130  
            has not been called since.
131  
    */
131  
    */
132  
    bool stopped() const noexcept
132  
    bool stopped() const noexcept
133  
    {
133  
    {
134  
        return sched_->stopped();
134  
        return sched_->stopped();
135  
    }
135  
    }
136  

136  

137  
    /** Restart the context after being stopped.
137  
    /** Restart the context after being stopped.
138  

138  

139  
        This function must be called before `run()` can be called
139  
        This function must be called before `run()` can be called
140  
        again after `stop()` has been called.
140  
        again after `stop()` has been called.
141  
    */
141  
    */
142  
    void restart()
142  
    void restart()
143  
    {
143  
    {
144  
        sched_->restart();
144  
        sched_->restart();
145  
    }
145  
    }
146  

146  

147  
    /** Process all pending work items.
147  
    /** Process all pending work items.
148  

148  

149  
        This function blocks until all pending work items have been
149  
        This function blocks until all pending work items have been
150  
        executed or `stop()` is called. The context is stopped
150  
        executed or `stop()` is called. The context is stopped
151  
        when there is no more outstanding work.
151  
        when there is no more outstanding work.
152  

152  

153  
        @note The context must be restarted with `restart()` before
153  
        @note The context must be restarted with `restart()` before
154  
            calling this function again after it returns.
154  
            calling this function again after it returns.
155  

155  

156  
        @return The number of handlers executed.
156  
        @return The number of handlers executed.
157  
    */
157  
    */
158  
    std::size_t run()
158  
    std::size_t run()
159  
    {
159  
    {
160  
        return sched_->run();
160  
        return sched_->run();
161  
    }
161  
    }
162  

162  

163  
    /** Process at most one pending work item.
163  
    /** Process at most one pending work item.
164  

164  

165  
        This function blocks until one work item has been executed
165  
        This function blocks until one work item has been executed
166  
        or `stop()` is called. The context is stopped when there
166  
        or `stop()` is called. The context is stopped when there
167  
        is no more outstanding work.
167  
        is no more outstanding work.
168  

168  

169  
        @note The context must be restarted with `restart()` before
169  
        @note The context must be restarted with `restart()` before
170  
            calling this function again after it returns.
170  
            calling this function again after it returns.
171  

171  

172  
        @return The number of handlers executed (0 or 1).
172  
        @return The number of handlers executed (0 or 1).
173  
    */
173  
    */
174  
    std::size_t run_one()
174  
    std::size_t run_one()
175  
    {
175  
    {
176  
        return sched_->run_one();
176  
        return sched_->run_one();
177  
    }
177  
    }
178  

178  

179  
    /** Process work items for the specified duration.
179  
    /** Process work items for the specified duration.
180  

180  

181  
        This function blocks until work items have been executed for
181  
        This function blocks until work items have been executed for
182  
        the specified duration, or `stop()` is called. The context
182  
        the specified duration, or `stop()` is called. The context
183  
        is stopped when there is no more outstanding work.
183  
        is stopped when there is no more outstanding work.
184  

184  

185  
        @note The context must be restarted with `restart()` before
185  
        @note The context must be restarted with `restart()` before
186  
            calling this function again after it returns.
186  
            calling this function again after it returns.
187  

187  

188  
        @param rel_time The duration for which to process work.
188  
        @param rel_time The duration for which to process work.
189  

189  

190  
        @return The number of handlers executed.
190  
        @return The number of handlers executed.
191  
    */
191  
    */
192  
    template<class Rep, class Period>
192  
    template<class Rep, class Period>
193  
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
193  
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
194  
    {
194  
    {
195  
        return run_until(std::chrono::steady_clock::now() + rel_time);
195  
        return run_until(std::chrono::steady_clock::now() + rel_time);
196  
    }
196  
    }
197  

197  

198  
    /** Process work items until the specified time.
198  
    /** Process work items until the specified time.
199  

199  

200  
        This function blocks until the specified time is reached
200  
        This function blocks until the specified time is reached
201  
        or `stop()` is called. The context is stopped when there
201  
        or `stop()` is called. The context is stopped when there
202  
        is no more outstanding work.
202  
        is no more outstanding work.
203  

203  

204  
        @note The context must be restarted with `restart()` before
204  
        @note The context must be restarted with `restart()` before
205  
            calling this function again after it returns.
205  
            calling this function again after it returns.
206  

206  

207  
        @param abs_time The time point until which to process work.
207  
        @param abs_time The time point until which to process work.
208  

208  

209  
        @return The number of handlers executed.
209  
        @return The number of handlers executed.
210  
    */
210  
    */
211  
    template<class Clock, class Duration>
211  
    template<class Clock, class Duration>
212  
    std::size_t
212  
    std::size_t
213  
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
213  
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
214  
    {
214  
    {
215  
        std::size_t n = 0;
215  
        std::size_t n = 0;
216  
        while (run_one_until(abs_time))
216  
        while (run_one_until(abs_time))
217  
            if (n != (std::numeric_limits<std::size_t>::max)())
217  
            if (n != (std::numeric_limits<std::size_t>::max)())
218  
                ++n;
218  
                ++n;
219  
        return n;
219  
        return n;
220  
    }
220  
    }
221  

221  

222  
    /** Process at most one work item for the specified duration.
222  
    /** Process at most one work item for the specified duration.
223  

223  

224  
        This function blocks until one work item has been executed,
224  
        This function blocks until one work item has been executed,
225  
        the specified duration has elapsed, or `stop()` is called.
225  
        the specified duration has elapsed, or `stop()` is called.
226  
        The context is stopped when there is no more outstanding work.
226  
        The context is stopped when there is no more outstanding work.
227  

227  

228  
        @note The context must be restarted with `restart()` before
228  
        @note The context must be restarted with `restart()` before
229  
            calling this function again after it returns.
229  
            calling this function again after it returns.
230  

230  

231  
        @param rel_time The duration for which the call may block.
231  
        @param rel_time The duration for which the call may block.
232  

232  

233  
        @return The number of handlers executed (0 or 1).
233  
        @return The number of handlers executed (0 or 1).
234  
    */
234  
    */
235  
    template<class Rep, class Period>
235  
    template<class Rep, class Period>
236  
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
236  
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
237  
    {
237  
    {
238  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
238  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
239  
    }
239  
    }
240  

240  

241  
    /** Process at most one work item until the specified time.
241  
    /** Process at most one work item until the specified time.
242  

242  

243  
        This function blocks until one work item has been executed,
243  
        This function blocks until one work item has been executed,
244  
        the specified time is reached, or `stop()` is called.
244  
        the specified time is reached, or `stop()` is called.
245  
        The context is stopped when there is no more outstanding work.
245  
        The context is stopped when there is no more outstanding work.
246  

246  

247  
        @note The context must be restarted with `restart()` before
247  
        @note The context must be restarted with `restart()` before
248  
            calling this function again after it returns.
248  
            calling this function again after it returns.
249  

249  

250  
        @param abs_time The time point until which the call may block.
250  
        @param abs_time The time point until which the call may block.
251  

251  

252  
        @return The number of handlers executed (0 or 1).
252  
        @return The number of handlers executed (0 or 1).
253  
    */
253  
    */
254  
    template<class Clock, class Duration>
254  
    template<class Clock, class Duration>
255  
    std::size_t
255  
    std::size_t
256  
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
256  
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
257  
    {
257  
    {
258  
        typename Clock::time_point now = Clock::now();
258  
        typename Clock::time_point now = Clock::now();
259  
        while (now < abs_time)
259  
        while (now < abs_time)
260  
        {
260  
        {
261  
            auto rel_time = abs_time - now;
261  
            auto rel_time = abs_time - now;
262  
            if (rel_time > std::chrono::seconds(1))
262  
            if (rel_time > std::chrono::seconds(1))
263  
                rel_time = std::chrono::seconds(1);
263  
                rel_time = std::chrono::seconds(1);
264  

264  

265  
            std::size_t s = sched_->wait_one(
265  
            std::size_t s = sched_->wait_one(
266  
                static_cast<long>(
266  
                static_cast<long>(
267  
                    std::chrono::duration_cast<std::chrono::microseconds>(
267  
                    std::chrono::duration_cast<std::chrono::microseconds>(
268  
                        rel_time)
268  
                        rel_time)
269  
                        .count()));
269  
                        .count()));
270  

270  

271  
            if (s || stopped())
271  
            if (s || stopped())
272  
                return s;
272  
                return s;
273  

273  

274  
            now = Clock::now();
274  
            now = Clock::now();
275  
        }
275  
        }
276  
        return 0;
276  
        return 0;
277  
    }
277  
    }
278  

278  

279  
    /** Process all ready work items without blocking.
279  
    /** Process all ready work items without blocking.
280  

280  

281  
        This function executes all work items that are ready to run
281  
        This function executes all work items that are ready to run
282  
        without blocking for more work. The context is stopped
282  
        without blocking for more work. The context is stopped
283  
        when there is no more outstanding work.
283  
        when there is no more outstanding work.
284  

284  

285  
        @note The context must be restarted with `restart()` before
285  
        @note The context must be restarted with `restart()` before
286  
            calling this function again after it returns.
286  
            calling this function again after it returns.
287  

287  

288  
        @return The number of handlers executed.
288  
        @return The number of handlers executed.
289  
    */
289  
    */
290  
    std::size_t poll()
290  
    std::size_t poll()
291  
    {
291  
    {
292  
        return sched_->poll();
292  
        return sched_->poll();
293  
    }
293  
    }
294  

294  

295  
    /** Process at most one ready work item without blocking.
295  
    /** Process at most one ready work item without blocking.
296  

296  

297  
        This function executes at most one work item that is ready
297  
        This function executes at most one work item that is ready
298  
        to run without blocking for more work. The context is
298  
        to run without blocking for more work. The context is
299  
        stopped when there is no more outstanding work.
299  
        stopped when there is no more outstanding work.
300  

300  

301  
        @note The context must be restarted with `restart()` before
301  
        @note The context must be restarted with `restart()` before
302  
            calling this function again after it returns.
302  
            calling this function again after it returns.
303  

303  

304  
        @return The number of handlers executed (0 or 1).
304  
        @return The number of handlers executed (0 or 1).
305  
    */
305  
    */
306  
    std::size_t poll_one()
306  
    std::size_t poll_one()
307  
    {
307  
    {
308  
        return sched_->poll_one();
308  
        return sched_->poll_one();
309  
    }
309  
    }
310  
};
310  
};
311  

311  

312  
/** An executor for dispatching work to an I/O context.
312  
/** An executor for dispatching work to an I/O context.
313  

313  

314  
    The executor provides the interface for posting work items and
314  
    The executor provides the interface for posting work items and
315  
    dispatching coroutines to the associated context. It satisfies
315  
    dispatching coroutines to the associated context. It satisfies
316  
    the `capy::Executor` concept.
316  
    the `capy::Executor` concept.
317  

317  

318  
    Executors are lightweight handles that can be copied and compared
318  
    Executors are lightweight handles that can be copied and compared
319  
    for equality. Two executors compare equal if they refer to the
319  
    for equality. Two executors compare equal if they refer to the
320  
    same context.
320  
    same context.
321  

321  

322  
    @par Thread Safety
322  
    @par Thread Safety
323  
    Distinct objects: Safe.@n
323  
    Distinct objects: Safe.@n
324  
    Shared objects: Safe.
324  
    Shared objects: Safe.
325  
*/
325  
*/
326  
class io_context::executor_type
326  
class io_context::executor_type
327  
{
327  
{
328  
    io_context* ctx_ = nullptr;
328  
    io_context* ctx_ = nullptr;
329  

329  

330  
public:
330  
public:
331  
    /** Default constructor.
331  
    /** Default constructor.
332  

332  

333  
        Constructs an executor not associated with any context.
333  
        Constructs an executor not associated with any context.
334  
    */
334  
    */
335  
    executor_type() = default;
335  
    executor_type() = default;
336  

336  

337  
    /** Construct an executor from a context.
337  
    /** Construct an executor from a context.
338  

338  

339  
        @param ctx The context to associate with this executor.
339  
        @param ctx The context to associate with this executor.
340  
    */
340  
    */
341  
    explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
341  
    explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
342  

342  

343  
    /** Return a reference to the associated execution context.
343  
    /** Return a reference to the associated execution context.
344  

344  

345  
        @return Reference to the context.
345  
        @return Reference to the context.
346  
    */
346  
    */
347  
    io_context& context() const noexcept
347  
    io_context& context() const noexcept
348  
    {
348  
    {
349  
        return *ctx_;
349  
        return *ctx_;
350  
    }
350  
    }
351  

351  

352  
    /** Check if the current thread is running this executor's context.
352  
    /** Check if the current thread is running this executor's context.
353  

353  

354  
        @return `true` if `run()` is being called on this thread.
354  
        @return `true` if `run()` is being called on this thread.
355  
    */
355  
    */
356  
    bool running_in_this_thread() const noexcept
356  
    bool running_in_this_thread() const noexcept
357  
    {
357  
    {
358  
        return ctx_->sched_->running_in_this_thread();
358  
        return ctx_->sched_->running_in_this_thread();
359  
    }
359  
    }
360  

360  

361  
    /** Informs the executor that work is beginning.
361  
    /** Informs the executor that work is beginning.
362  

362  

363  
        Must be paired with `on_work_finished()`.
363  
        Must be paired with `on_work_finished()`.
364  
    */
364  
    */
365  
    void on_work_started() const noexcept
365  
    void on_work_started() const noexcept
366  
    {
366  
    {
367  
        ctx_->sched_->work_started();
367  
        ctx_->sched_->work_started();
368  
    }
368  
    }
369  

369  

370  
    /** Informs the executor that work has completed.
370  
    /** Informs the executor that work has completed.
371  

371  

372  
        @par Preconditions
372  
        @par Preconditions
373  
        A preceding call to `on_work_started()` on an equal executor.
373  
        A preceding call to `on_work_started()` on an equal executor.
374  
    */
374  
    */
375  
    void on_work_finished() const noexcept
375  
    void on_work_finished() const noexcept
376  
    {
376  
    {
377  
        ctx_->sched_->work_finished();
377  
        ctx_->sched_->work_finished();
378  
    }
378  
    }
379  

379  

380  
    /** Dispatch a continuation.
380  
    /** Dispatch a continuation.
381  

381  

382  
        Returns a handle for symmetric transfer. If called from
382  
        Returns a handle for symmetric transfer. If called from
383  
        within `run()`, returns `c.h`. Otherwise posts the
383  
        within `run()`, returns `c.h`. Otherwise posts the
384  
        enclosing continuation_op as a scheduler_op for later
384  
        enclosing continuation_op as a scheduler_op for later
385  
        execution and returns `std::noop_coroutine()`.
385  
        execution and returns `std::noop_coroutine()`.
386  

386  

387  
        @param c The continuation to dispatch. Must be the `cont`
387  
        @param c The continuation to dispatch. Must be the `cont`
388  
                 member of a `detail::continuation_op`.
388  
                 member of a `detail::continuation_op`.
389  

389  

390  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
390  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
391  
    */
391  
    */
392  
    std::coroutine_handle<> dispatch(capy::continuation& c) const
392  
    std::coroutine_handle<> dispatch(capy::continuation& c) const
393  
    {
393  
    {
394  
        if (running_in_this_thread())
394  
        if (running_in_this_thread())
395  
            return c.h;
395  
            return c.h;
396  
        post(c);
396  
        post(c);
397  
        return std::noop_coroutine();
397  
        return std::noop_coroutine();
398  
    }
398  
    }
399  

399  

400  
    /** Post a continuation for deferred execution.
400  
    /** Post a continuation for deferred execution.
401  

401  

402  
        If the continuation is backed by a continuation_op
402  
        If the continuation is backed by a continuation_op
403  
        (tagged), posts it directly as a scheduler_op — zero
403  
        (tagged), posts it directly as a scheduler_op — zero
404  
        heap allocation. Otherwise falls back to the
404  
        heap allocation. Otherwise falls back to the
405  
        heap-allocating post(coroutine_handle<>) path.
405  
        heap-allocating post(coroutine_handle<>) path.
406  
    */
406  
    */
407  
    void post(capy::continuation& c) const
407  
    void post(capy::continuation& c) const
408  
    {
408  
    {
409  
        auto* op = detail::continuation_op::try_from_continuation(c);
409  
        auto* op = detail::continuation_op::try_from_continuation(c);
410  
        if (op)
410  
        if (op)
411  
            ctx_->sched_->post(op);
411  
            ctx_->sched_->post(op);
412  
        else
412  
        else
413  
            ctx_->sched_->post(c.h);
413  
            ctx_->sched_->post(c.h);
414  
    }
414  
    }
415  

415  

416  
    /** Post a bare coroutine handle for deferred execution.
416  
    /** Post a bare coroutine handle for deferred execution.
417  

417  

418  
        Heap-allocates a scheduler_op to wrap the handle. Prefer
418  
        Heap-allocates a scheduler_op to wrap the handle. Prefer
419  
        posting through a continuation_op-backed continuation when
419  
        posting through a continuation_op-backed continuation when
420  
        the continuation has suitable lifetime.
420  
        the continuation has suitable lifetime.
421  

421  

422  
        @param h The coroutine handle to post.
422  
        @param h The coroutine handle to post.
423  
    */
423  
    */
424  
    void post(std::coroutine_handle<> h) const
424  
    void post(std::coroutine_handle<> h) const
425  
    {
425  
    {
426  
        ctx_->sched_->post(h);
426  
        ctx_->sched_->post(h);
427  
    }
427  
    }
428  

428  

429  
    /** Compare two executors for equality.
429  
    /** Compare two executors for equality.
430  

430  

431  
        @return `true` if both executors refer to the same context.
431  
        @return `true` if both executors refer to the same context.
432  
    */
432  
    */
433  
    bool operator==(executor_type const& other) const noexcept
433  
    bool operator==(executor_type const& other) const noexcept
434  
    {
434  
    {
435  
        return ctx_ == other.ctx_;
435  
        return ctx_ == other.ctx_;
436  
    }
436  
    }
437  

437  

438  
    /** Compare two executors for inequality.
438  
    /** Compare two executors for inequality.
439  

439  

440  
        @return `true` if the executors refer to different contexts.
440  
        @return `true` if the executors refer to different contexts.
441  
    */
441  
    */
442  
    bool operator!=(executor_type const& other) const noexcept
442  
    bool operator!=(executor_type const& other) const noexcept
443  
    {
443  
    {
444  
        return ctx_ != other.ctx_;
444  
        return ctx_ != other.ctx_;
445  
    }
445  
    }
446  
};
446  
};
447  

447  

448  
inline io_context::executor_type
448  
inline io_context::executor_type
449  
io_context::get_executor() const noexcept
449  
io_context::get_executor() const noexcept
450  
{
450  
{
451  
    return executor_type(const_cast<io_context&>(*this));
451  
    return executor_type(const_cast<io_context&>(*this));
452  
}
452  
}
453  

453  

454  
} // namespace boost::corosio
454  
} // namespace boost::corosio
455  

455  

456  
#endif // BOOST_COROSIO_IO_CONTEXT_HPP
456  
#endif // BOOST_COROSIO_IO_CONTEXT_HPP