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  
/** Runtime tuning options for @ref io_context.
30  
/** Runtime tuning options for @ref io_context.
31  

31  

32  
    All fields have defaults that match the library's built-in
32  
    All fields have defaults that match the library's built-in
33  
    values, so constructing a default `io_context_options` produces
33  
    values, so constructing a default `io_context_options` produces
34  
    identical behavior to an unconfigured context.
34  
    identical behavior to an unconfigured context.
35  

35  

36  
    Options that apply only to a specific backend family are
36  
    Options that apply only to a specific backend family are
37  
    silently ignored when the active backend does not support them.
37  
    silently ignored when the active backend does not support them.
38  

38  

39  
    @par Example
39  
    @par Example
40  
    @code
40  
    @code
41  
    io_context_options opts;
41  
    io_context_options opts;
42  
    opts.max_events_per_poll  = 256;   // larger batch per syscall
42  
    opts.max_events_per_poll  = 256;   // larger batch per syscall
43  
    opts.inline_budget_max    = 32;    // more speculative completions
43  
    opts.inline_budget_max    = 32;    // more speculative completions
44  
    opts.thread_pool_size     = 4;     // more file-I/O workers
44  
    opts.thread_pool_size     = 4;     // more file-I/O workers
45  

45  

46  
    io_context ioc(opts);
46  
    io_context ioc(opts);
47  
    @endcode
47  
    @endcode
48  

48  

49  
    @see io_context, native_io_context
49  
    @see io_context, native_io_context
50  
*/
50  
*/
51  
struct io_context_options
51  
struct io_context_options
52  
{
52  
{
53  
    /** Maximum events fetched per reactor poll call.
53  
    /** Maximum events fetched per reactor poll call.
54  

54  

55  
        Controls the buffer size passed to `epoll_wait()` or
55  
        Controls the buffer size passed to `epoll_wait()` or
56  
        `kevent()`. Larger values reduce syscall frequency under
56  
        `kevent()`. Larger values reduce syscall frequency under
57  
        high load; smaller values improve fairness between
57  
        high load; smaller values improve fairness between
58  
        connections. Ignored on IOCP and select backends.
58  
        connections. Ignored on IOCP and select backends.
59  
    */
59  
    */
60  
    unsigned max_events_per_poll = 128;
60  
    unsigned max_events_per_poll = 128;
61  

61  

62  
    /** Starting inline completion budget per handler chain.
62  
    /** Starting inline completion budget per handler chain.
63  

63  

64  
        After a posted handler executes, the reactor grants this
64  
        After a posted handler executes, the reactor grants this
65  
        many speculative inline completions before forcing a
65  
        many speculative inline completions before forcing a
66  
        re-queue. Applies to reactor backends only.
66  
        re-queue. Applies to reactor backends only.
67  
    */
67  
    */
68  
    unsigned inline_budget_initial = 2;
68  
    unsigned inline_budget_initial = 2;
69  

69  

70  
    /** Hard ceiling on adaptive inline budget ramp-up.
70  
    /** Hard ceiling on adaptive inline budget ramp-up.
71  

71  

72  
        The budget doubles each cycle it is fully consumed, up to
72  
        The budget doubles each cycle it is fully consumed, up to
73  
        this limit. Applies to reactor backends only.
73  
        this limit. Applies to reactor backends only.
74  
    */
74  
    */
75  
    unsigned inline_budget_max = 16;
75  
    unsigned inline_budget_max = 16;
76  

76  

77  
    /** Inline budget when no other thread assists the reactor.
77  
    /** Inline budget when no other thread assists the reactor.
78  

78  

79  
        When only one thread is running the event loop, this
79  
        When only one thread is running the event loop, this
80  
        value caps the inline budget to preserve fairness.
80  
        value caps the inline budget to preserve fairness.
81  
        Applies to reactor backends only.
81  
        Applies to reactor backends only.
82  
    */
82  
    */
83  
    unsigned unassisted_budget = 4;
83  
    unsigned unassisted_budget = 4;
84  

84  

85  
    /** Maximum `GetQueuedCompletionStatus` timeout in milliseconds.
85  
    /** Maximum `GetQueuedCompletionStatus` timeout in milliseconds.
86  

86  

87  
        Bounds how long the IOCP scheduler blocks between timer
87  
        Bounds how long the IOCP scheduler blocks between timer
88  
        rechecks. Lower values improve timer responsiveness at the
88  
        rechecks. Lower values improve timer responsiveness at the
89  
        cost of more syscalls. Applies to IOCP only.
89  
        cost of more syscalls. Applies to IOCP only.
90  
    */
90  
    */
91  
    unsigned gqcs_timeout_ms = 500;
91  
    unsigned gqcs_timeout_ms = 500;
92  

92  

93  
    /** Thread pool size for blocking I/O (file I/O, DNS resolution).
93  
    /** Thread pool size for blocking I/O (file I/O, DNS resolution).
94  

94  

95  
        Sets the number of worker threads in the shared thread pool
95  
        Sets the number of worker threads in the shared thread pool
96  
        used by POSIX file services and DNS resolution. Must be at
96  
        used by POSIX file services and DNS resolution. Must be at
97  
        least 1. Applies to POSIX backends only; ignored on IOCP
97  
        least 1. Applies to POSIX backends only; ignored on IOCP
98  
        where file I/O uses native overlapped I/O.
98  
        where file I/O uses native overlapped I/O.
99  
    */
99  
    */
100  
    unsigned thread_pool_size = 1;
100  
    unsigned thread_pool_size = 1;
101  

101  

102  
    /** Enable single-threaded mode (disable scheduler locking).
102  
    /** Enable single-threaded mode (disable scheduler locking).
103  

103  

104  
        When true, the scheduler skips all mutex lock/unlock and
104  
        When true, the scheduler skips all mutex lock/unlock and
105  
        condition variable operations on the hot path. This
105  
        condition variable operations on the hot path. This
106  
        eliminates synchronization overhead when only one thread
106  
        eliminates synchronization overhead when only one thread
107  
        calls `run()`.
107  
        calls `run()`.
108  

108  

109  
        @par Restrictions
109  
        @par Restrictions
110  
        - Only one thread may call `run()` (or any run variant).
110  
        - Only one thread may call `run()` (or any run variant).
111  
        - Posting work from another thread is undefined behavior.
111  
        - Posting work from another thread is undefined behavior.
112  
        - DNS resolution returns `operation_not_supported`.
112  
        - DNS resolution returns `operation_not_supported`.
113  
        - POSIX file I/O returns `operation_not_supported`.
113  
        - POSIX file I/O returns `operation_not_supported`.
114  
        - Signal sets should not be shared across contexts.
114  
        - Signal sets should not be shared across contexts.
115  
    */
115  
    */
116  
    bool single_threaded = false;
116  
    bool single_threaded = false;
117  
};
117  
};
118  

118  

119 -
class timer_service;
 
120  
namespace detail {
119  
namespace detail {
121  
struct timer_service_access;
120  
struct timer_service_access;
122  
} // namespace detail
121  
} // namespace detail
123  

122  

124  
/** An I/O context for running asynchronous operations.
123  
/** An I/O context for running asynchronous operations.
125  

124  

126  
    The io_context provides an execution environment for async
125  
    The io_context provides an execution environment for async
127  
    operations. It maintains a queue of pending work items and
126  
    operations. It maintains a queue of pending work items and
128  
    processes them when `run()` is called.
127  
    processes them when `run()` is called.
129  

128  

130  
    The default and unsigned constructors select the platform's
129  
    The default and unsigned constructors select the platform's
131  
    native backend:
130  
    native backend:
132  
    - Windows: IOCP
131  
    - Windows: IOCP
133  
    - Linux: epoll
132  
    - Linux: epoll
134  
    - BSD/macOS: kqueue
133  
    - BSD/macOS: kqueue
135  
    - Other POSIX: select
134  
    - Other POSIX: select
136  

135  

137  
    The template constructor accepts a backend tag value to
136  
    The template constructor accepts a backend tag value to
138  
    choose a specific backend at compile time:
137  
    choose a specific backend at compile time:
139  

138  

140  
    @par Example
139  
    @par Example
141  
    @code
140  
    @code
142  
    io_context ioc;                   // platform default
141  
    io_context ioc;                   // platform default
143  
    io_context ioc2(corosio::epoll);  // explicit backend
142  
    io_context ioc2(corosio::epoll);  // explicit backend
144  
    @endcode
143  
    @endcode
145  

144  

146  
    @par Thread Safety
145  
    @par Thread Safety
147  
    Distinct objects: Safe.@n
146  
    Distinct objects: Safe.@n
148  
    Shared objects: Safe, if using a concurrency hint greater
147  
    Shared objects: Safe, if using a concurrency hint greater
149  
    than 1.
148  
    than 1.
150  

149  

151  
    @see epoll_t, select_t, kqueue_t, iocp_t
150  
    @see epoll_t, select_t, kqueue_t, iocp_t
152  
*/
151  
*/
153  
class BOOST_COROSIO_DECL io_context : public capy::execution_context
152  
class BOOST_COROSIO_DECL io_context : public capy::execution_context
154  
{
153  
{
155  
    friend struct detail::timer_service_access;
154  
    friend struct detail::timer_service_access;
156  

155  

157  
    /// Pre-create services that depend on options (before construct).
156  
    /// Pre-create services that depend on options (before construct).
158  
    void apply_options_pre_(io_context_options const& opts);
157  
    void apply_options_pre_(io_context_options const& opts);
159  

158  

160  
    /// Apply runtime tuning to the scheduler (after construct).
159  
    /// Apply runtime tuning to the scheduler (after construct).
161  
    void apply_options_post_(io_context_options const& opts);
160  
    void apply_options_post_(io_context_options const& opts);
162  

161  

163 -
    detail::timer_service* timer_svc_ = nullptr;
 
164  
protected:
162  
protected:
165  
    detail::scheduler* sched_;
163  
    detail::scheduler* sched_;
166  

164  

167  
public:
165  
public:
168  
    /** The executor type for this context. */
166  
    /** The executor type for this context. */
169  
    class executor_type;
167  
    class executor_type;
170  

168  

171  
    /** Construct with default concurrency and platform backend. */
169  
    /** Construct with default concurrency and platform backend. */
172  
    io_context();
170  
    io_context();
173  

171  

174  
    /** Construct with a concurrency hint and platform backend.
172  
    /** Construct with a concurrency hint and platform backend.
175  

173  

176  
        @param concurrency_hint Hint for the number of threads
174  
        @param concurrency_hint Hint for the number of threads
177  
            that will call `run()`.
175  
            that will call `run()`.
178  
    */
176  
    */
179  
    explicit io_context(unsigned concurrency_hint);
177  
    explicit io_context(unsigned concurrency_hint);
180  

178  

181  
    /** Construct with runtime tuning options and platform backend.
179  
    /** Construct with runtime tuning options and platform backend.
182  

180  

183  
        @param opts Runtime options controlling scheduler and
181  
        @param opts Runtime options controlling scheduler and
184  
            service behavior.
182  
            service behavior.
185  
        @param concurrency_hint Hint for the number of threads
183  
        @param concurrency_hint Hint for the number of threads
186  
            that will call `run()`.
184  
            that will call `run()`.
187  
    */
185  
    */
188  
    explicit io_context(
186  
    explicit io_context(
189  
        io_context_options const& opts,
187  
        io_context_options const& opts,
190  
        unsigned concurrency_hint = std::thread::hardware_concurrency());
188  
        unsigned concurrency_hint = std::thread::hardware_concurrency());
191  

189  

192  
    /** Construct with an explicit backend tag.
190  
    /** Construct with an explicit backend tag.
193  

191  

194  
        @param backend The backend tag value selecting the I/O
192  
        @param backend The backend tag value selecting the I/O
195  
            multiplexer (e.g. `corosio::epoll`).
193  
            multiplexer (e.g. `corosio::epoll`).
196  
        @param concurrency_hint Hint for the number of threads
194  
        @param concurrency_hint Hint for the number of threads
197  
            that will call `run()`.
195  
            that will call `run()`.
198  
    */
196  
    */
199  
    template<class Backend>
197  
    template<class Backend>
200  
        requires requires { Backend::construct; }
198  
        requires requires { Backend::construct; }
201  
    explicit io_context(
199  
    explicit io_context(
202  
        Backend backend,
200  
        Backend backend,
203  
        unsigned concurrency_hint = std::thread::hardware_concurrency())
201  
        unsigned concurrency_hint = std::thread::hardware_concurrency())
204  
        : capy::execution_context(this)
202  
        : capy::execution_context(this)
205  
        , sched_(nullptr)
203  
        , sched_(nullptr)
206  
    {
204  
    {
207  
        (void)backend;
205  
        (void)backend;
208  
        sched_ = &Backend::construct(*this, concurrency_hint);
206  
        sched_ = &Backend::construct(*this, concurrency_hint);
209  
    }
207  
    }
210  

208  

211  
    /** Construct with an explicit backend tag and runtime options.
209  
    /** Construct with an explicit backend tag and runtime options.
212  

210  

213  
        @param backend The backend tag value selecting the I/O
211  
        @param backend The backend tag value selecting the I/O
214  
            multiplexer (e.g. `corosio::epoll`).
212  
            multiplexer (e.g. `corosio::epoll`).
215  
        @param opts Runtime options controlling scheduler and
213  
        @param opts Runtime options controlling scheduler and
216  
            service behavior.
214  
            service behavior.
217  
        @param concurrency_hint Hint for the number of threads
215  
        @param concurrency_hint Hint for the number of threads
218  
            that will call `run()`.
216  
            that will call `run()`.
219  
    */
217  
    */
220  
    template<class Backend>
218  
    template<class Backend>
221  
        requires requires { Backend::construct; }
219  
        requires requires { Backend::construct; }
222  
    explicit io_context(
220  
    explicit io_context(
223  
        Backend backend,
221  
        Backend backend,
224  
        io_context_options const& opts,
222  
        io_context_options const& opts,
225  
        unsigned concurrency_hint = std::thread::hardware_concurrency())
223  
        unsigned concurrency_hint = std::thread::hardware_concurrency())
226  
        : capy::execution_context(this)
224  
        : capy::execution_context(this)
227  
        , sched_(nullptr)
225  
        , sched_(nullptr)
228  
    {
226  
    {
229  
        (void)backend;
227  
        (void)backend;
230  
        apply_options_pre_(opts);
228  
        apply_options_pre_(opts);
231  
        sched_ = &Backend::construct(*this, concurrency_hint);
229  
        sched_ = &Backend::construct(*this, concurrency_hint);
232  
        apply_options_post_(opts);
230  
        apply_options_post_(opts);
233  
    }
231  
    }
234  

232  

235  
    ~io_context();
233  
    ~io_context();
236  

234  

237  
    io_context(io_context const&)            = delete;
235  
    io_context(io_context const&)            = delete;
238  
    io_context& operator=(io_context const&) = delete;
236  
    io_context& operator=(io_context const&) = delete;
239  

237  

240  
    /** Return an executor for this context.
238  
    /** Return an executor for this context.
241  

239  

242  
        The returned executor can be used to dispatch coroutines
240  
        The returned executor can be used to dispatch coroutines
243  
        and post work items to this context.
241  
        and post work items to this context.
244  

242  

245  
        @return An executor associated with this context.
243  
        @return An executor associated with this context.
246  
    */
244  
    */
247  
    executor_type get_executor() const noexcept;
245  
    executor_type get_executor() const noexcept;
248  

246  

249  
    /** Signal the context to stop processing.
247  
    /** Signal the context to stop processing.
250  

248  

251  
        This causes `run()` to return as soon as possible. Any pending
249  
        This causes `run()` to return as soon as possible. Any pending
252  
        work items remain queued.
250  
        work items remain queued.
253  
    */
251  
    */
254  
    void stop()
252  
    void stop()
255  
    {
253  
    {
256  
        sched_->stop();
254  
        sched_->stop();
257  
    }
255  
    }
258  

256  

259  
    /** Return whether the context has been stopped.
257  
    /** Return whether the context has been stopped.
260  

258  

261  
        @return `true` if `stop()` has been called and `restart()`
259  
        @return `true` if `stop()` has been called and `restart()`
262  
            has not been called since.
260  
            has not been called since.
263  
    */
261  
    */
264  
    bool stopped() const noexcept
262  
    bool stopped() const noexcept
265  
    {
263  
    {
266  
        return sched_->stopped();
264  
        return sched_->stopped();
267  
    }
265  
    }
268  

266  

269  
    /** Restart the context after being stopped.
267  
    /** Restart the context after being stopped.
270  

268  

271  
        This function must be called before `run()` can be called
269  
        This function must be called before `run()` can be called
272  
        again after `stop()` has been called.
270  
        again after `stop()` has been called.
273  
    */
271  
    */
274  
    void restart()
272  
    void restart()
275  
    {
273  
    {
276  
        sched_->restart();
274  
        sched_->restart();
277  
    }
275  
    }
278  

276  

279  
    /** Process all pending work items.
277  
    /** Process all pending work items.
280  

278  

281  
        This function blocks until all pending work items have been
279  
        This function blocks until all pending work items have been
282  
        executed or `stop()` is called. The context is stopped
280  
        executed or `stop()` is called. The context is stopped
283  
        when there is no more outstanding work.
281  
        when there is no more outstanding work.
284  

282  

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

285  

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

292  

295  
    /** Process at most one pending work item.
293  
    /** Process at most one pending work item.
296  

294  

297  
        This function blocks until one work item has been executed
295  
        This function blocks until one work item has been executed
298  
        or `stop()` is called. The context is stopped when there
296  
        or `stop()` is called. The context is stopped when there
299  
        is no more outstanding work.
297  
        is no more outstanding work.
300  

298  

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

301  

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

308  

311  
    /** Process work items for the specified duration.
309  
    /** Process work items for the specified duration.
312  

310  

313  
        This function blocks until work items have been executed for
311  
        This function blocks until work items have been executed for
314  
        the specified duration, or `stop()` is called. The context
312  
        the specified duration, or `stop()` is called. The context
315  
        is stopped when there is no more outstanding work.
313  
        is stopped when there is no more outstanding work.
316  

314  

317  
        @note The context must be restarted with `restart()` before
315  
        @note The context must be restarted with `restart()` before
318  
            calling this function again after it returns.
316  
            calling this function again after it returns.
319  

317  

320  
        @param rel_time The duration for which to process work.
318  
        @param rel_time The duration for which to process work.
321  

319  

322  
        @return The number of handlers executed.
320  
        @return The number of handlers executed.
323  
    */
321  
    */
324  
    template<class Rep, class Period>
322  
    template<class Rep, class Period>
325  
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
323  
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
326  
    {
324  
    {
327  
        return run_until(std::chrono::steady_clock::now() + rel_time);
325  
        return run_until(std::chrono::steady_clock::now() + rel_time);
328  
    }
326  
    }
329  

327  

330  
    /** Process work items until the specified time.
328  
    /** Process work items until the specified time.
331  

329  

332  
        This function blocks until the specified time is reached
330  
        This function blocks until the specified time is reached
333  
        or `stop()` is called. The context is stopped when there
331  
        or `stop()` is called. The context is stopped when there
334  
        is no more outstanding work.
332  
        is no more outstanding work.
335  

333  

336  
        @note The context must be restarted with `restart()` before
334  
        @note The context must be restarted with `restart()` before
337  
            calling this function again after it returns.
335  
            calling this function again after it returns.
338  

336  

339  
        @param abs_time The time point until which to process work.
337  
        @param abs_time The time point until which to process work.
340  

338  

341  
        @return The number of handlers executed.
339  
        @return The number of handlers executed.
342  
    */
340  
    */
343  
    template<class Clock, class Duration>
341  
    template<class Clock, class Duration>
344  
    std::size_t
342  
    std::size_t
345  
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
343  
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
346  
    {
344  
    {
347  
        std::size_t n = 0;
345  
        std::size_t n = 0;
348  
        while (run_one_until(abs_time))
346  
        while (run_one_until(abs_time))
349  
            if (n != (std::numeric_limits<std::size_t>::max)())
347  
            if (n != (std::numeric_limits<std::size_t>::max)())
350  
                ++n;
348  
                ++n;
351  
        return n;
349  
        return n;
352  
    }
350  
    }
353  

351  

354  
    /** Process at most one work item for the specified duration.
352  
    /** Process at most one work item for the specified duration.
355  

353  

356  
        This function blocks until one work item has been executed,
354  
        This function blocks until one work item has been executed,
357  
        the specified duration has elapsed, or `stop()` is called.
355  
        the specified duration has elapsed, or `stop()` is called.
358  
        The context is stopped when there is no more outstanding work.
356  
        The context is stopped when there is no more outstanding work.
359  

357  

360  
        @note The context must be restarted with `restart()` before
358  
        @note The context must be restarted with `restart()` before
361  
            calling this function again after it returns.
359  
            calling this function again after it returns.
362  

360  

363  
        @param rel_time The duration for which the call may block.
361  
        @param rel_time The duration for which the call may block.
364  

362  

365  
        @return The number of handlers executed (0 or 1).
363  
        @return The number of handlers executed (0 or 1).
366  
    */
364  
    */
367  
    template<class Rep, class Period>
365  
    template<class Rep, class Period>
368  
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
366  
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
369  
    {
367  
    {
370  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
368  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
371  
    }
369  
    }
372  

370  

373  
    /** Process at most one work item until the specified time.
371  
    /** Process at most one work item until the specified time.
374  

372  

375  
        This function blocks until one work item has been executed,
373  
        This function blocks until one work item has been executed,
376  
        the specified time is reached, or `stop()` is called.
374  
        the specified time is reached, or `stop()` is called.
377  
        The context is stopped when there is no more outstanding work.
375  
        The context is stopped when there is no more outstanding work.
378  

376  

379  
        @note The context must be restarted with `restart()` before
377  
        @note The context must be restarted with `restart()` before
380  
            calling this function again after it returns.
378  
            calling this function again after it returns.
381  

379  

382  
        @param abs_time The time point until which the call may block.
380  
        @param abs_time The time point until which the call may block.
383  

381  

384  
        @return The number of handlers executed (0 or 1).
382  
        @return The number of handlers executed (0 or 1).
385  
    */
383  
    */
386  
    template<class Clock, class Duration>
384  
    template<class Clock, class Duration>
387  
    std::size_t
385  
    std::size_t
388  
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
386  
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
389  
    {
387  
    {
390  
        typename Clock::time_point now = Clock::now();
388  
        typename Clock::time_point now = Clock::now();
391  
        while (now < abs_time)
389  
        while (now < abs_time)
392  
        {
390  
        {
393  
            auto rel_time = abs_time - now;
391  
            auto rel_time = abs_time - now;
394  
            if (rel_time > std::chrono::seconds(1))
392  
            if (rel_time > std::chrono::seconds(1))
395  
                rel_time = std::chrono::seconds(1);
393  
                rel_time = std::chrono::seconds(1);
396  

394  

397  
            std::size_t s = sched_->wait_one(
395  
            std::size_t s = sched_->wait_one(
398  
                static_cast<long>(
396  
                static_cast<long>(
399  
                    std::chrono::duration_cast<std::chrono::microseconds>(
397  
                    std::chrono::duration_cast<std::chrono::microseconds>(
400  
                        rel_time)
398  
                        rel_time)
401  
                        .count()));
399  
                        .count()));
402  

400  

403  
            if (s || stopped())
401  
            if (s || stopped())
404  
                return s;
402  
                return s;
405  

403  

406  
            now = Clock::now();
404  
            now = Clock::now();
407  
        }
405  
        }
408  
        return 0;
406  
        return 0;
409  
    }
407  
    }
410  

408  

411  
    /** Process all ready work items without blocking.
409  
    /** Process all ready work items without blocking.
412  

410  

413  
        This function executes all work items that are ready to run
411  
        This function executes all work items that are ready to run
414  
        without blocking for more work. The context is stopped
412  
        without blocking for more work. The context is stopped
415  
        when there is no more outstanding work.
413  
        when there is no more outstanding work.
416  

414  

417  
        @note The context must be restarted with `restart()` before
415  
        @note The context must be restarted with `restart()` before
418  
            calling this function again after it returns.
416  
            calling this function again after it returns.
419  

417  

420  
        @return The number of handlers executed.
418  
        @return The number of handlers executed.
421  
    */
419  
    */
422  
    std::size_t poll()
420  
    std::size_t poll()
423  
    {
421  
    {
424  
        return sched_->poll();
422  
        return sched_->poll();
425  
    }
423  
    }
426  

424  

427  
    /** Process at most one ready work item without blocking.
425  
    /** Process at most one ready work item without blocking.
428  

426  

429  
        This function executes at most one work item that is ready
427  
        This function executes at most one work item that is ready
430  
        to run without blocking for more work. The context is
428  
        to run without blocking for more work. The context is
431  
        stopped when there is no more outstanding work.
429  
        stopped when there is no more outstanding work.
432  

430  

433  
        @note The context must be restarted with `restart()` before
431  
        @note The context must be restarted with `restart()` before
434  
            calling this function again after it returns.
432  
            calling this function again after it returns.
435  

433  

436  
        @return The number of handlers executed (0 or 1).
434  
        @return The number of handlers executed (0 or 1).
437  
    */
435  
    */
438  
    std::size_t poll_one()
436  
    std::size_t poll_one()
439  
    {
437  
    {
440  
        return sched_->poll_one();
438  
        return sched_->poll_one();
441  
    }
439  
    }
442  
};
440  
};
443  

441  

444  
/** An executor for dispatching work to an I/O context.
442  
/** An executor for dispatching work to an I/O context.
445  

443  

446  
    The executor provides the interface for posting work items and
444  
    The executor provides the interface for posting work items and
447  
    dispatching coroutines to the associated context. It satisfies
445  
    dispatching coroutines to the associated context. It satisfies
448  
    the `capy::Executor` concept.
446  
    the `capy::Executor` concept.
449  

447  

450  
    Executors are lightweight handles that can be copied and compared
448  
    Executors are lightweight handles that can be copied and compared
451  
    for equality. Two executors compare equal if they refer to the
449  
    for equality. Two executors compare equal if they refer to the
452  
    same context.
450  
    same context.
453  

451  

454  
    @par Thread Safety
452  
    @par Thread Safety
455  
    Distinct objects: Safe.@n
453  
    Distinct objects: Safe.@n
456  
    Shared objects: Safe.
454  
    Shared objects: Safe.
457  
*/
455  
*/
458  
class io_context::executor_type
456  
class io_context::executor_type
459  
{
457  
{
460  
    io_context* ctx_ = nullptr;
458  
    io_context* ctx_ = nullptr;
461  

459  

462  
public:
460  
public:
463  
    /** Default constructor.
461  
    /** Default constructor.
464  

462  

465  
        Constructs an executor not associated with any context.
463  
        Constructs an executor not associated with any context.
466  
    */
464  
    */
467  
    executor_type() = default;
465  
    executor_type() = default;
468  

466  

469  
    /** Construct an executor from a context.
467  
    /** Construct an executor from a context.
470  

468  

471  
        @param ctx The context to associate with this executor.
469  
        @param ctx The context to associate with this executor.
472  
    */
470  
    */
473  
    explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
471  
    explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
474  

472  

475  
    /** Return a reference to the associated execution context.
473  
    /** Return a reference to the associated execution context.
476  

474  

477  
        @return Reference to the context.
475  
        @return Reference to the context.
478  
    */
476  
    */
479  
    io_context& context() const noexcept
477  
    io_context& context() const noexcept
480  
    {
478  
    {
481  
        return *ctx_;
479  
        return *ctx_;
482  
    }
480  
    }
483  

481  

484  
    /** Check if the current thread is running this executor's context.
482  
    /** Check if the current thread is running this executor's context.
485  

483  

486  
        @return `true` if `run()` is being called on this thread.
484  
        @return `true` if `run()` is being called on this thread.
487  
    */
485  
    */
488  
    bool running_in_this_thread() const noexcept
486  
    bool running_in_this_thread() const noexcept
489  
    {
487  
    {
490  
        return ctx_->sched_->running_in_this_thread();
488  
        return ctx_->sched_->running_in_this_thread();
491  
    }
489  
    }
492  

490  

493  
    /** Informs the executor that work is beginning.
491  
    /** Informs the executor that work is beginning.
494  

492  

495  
        Must be paired with `on_work_finished()`.
493  
        Must be paired with `on_work_finished()`.
496  
    */
494  
    */
497  
    void on_work_started() const noexcept
495  
    void on_work_started() const noexcept
498  
    {
496  
    {
499  
        ctx_->sched_->work_started();
497  
        ctx_->sched_->work_started();
500  
    }
498  
    }
501  

499  

502  
    /** Informs the executor that work has completed.
500  
    /** Informs the executor that work has completed.
503  

501  

504  
        @par Preconditions
502  
        @par Preconditions
505  
        A preceding call to `on_work_started()` on an equal executor.
503  
        A preceding call to `on_work_started()` on an equal executor.
506  
    */
504  
    */
507  
    void on_work_finished() const noexcept
505  
    void on_work_finished() const noexcept
508  
    {
506  
    {
509  
        ctx_->sched_->work_finished();
507  
        ctx_->sched_->work_finished();
510  
    }
508  
    }
511  

509  

512  
    /** Dispatch a continuation.
510  
    /** Dispatch a continuation.
513  

511  

514  
        Returns a handle for symmetric transfer. If called from
512  
        Returns a handle for symmetric transfer. If called from
515  
        within `run()`, returns `c.h`. Otherwise posts the
513  
        within `run()`, returns `c.h`. Otherwise posts the
516  
        enclosing continuation_op as a scheduler_op for later
514  
        enclosing continuation_op as a scheduler_op for later
517  
        execution and returns `std::noop_coroutine()`.
515  
        execution and returns `std::noop_coroutine()`.
518  

516  

519  
        @param c The continuation to dispatch. Must be the `cont`
517  
        @param c The continuation to dispatch. Must be the `cont`
520  
                 member of a `detail::continuation_op`.
518  
                 member of a `detail::continuation_op`.
521  

519  

522  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
520  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
523  
    */
521  
    */
524  
    std::coroutine_handle<> dispatch(capy::continuation& c) const
522  
    std::coroutine_handle<> dispatch(capy::continuation& c) const
525  
    {
523  
    {
526  
        if (running_in_this_thread())
524  
        if (running_in_this_thread())
527  
            return c.h;
525  
            return c.h;
528  
        post(c);
526  
        post(c);
529  
        return std::noop_coroutine();
527  
        return std::noop_coroutine();
530  
    }
528  
    }
531  

529  

532  
    /** Post a continuation for deferred execution.
530  
    /** Post a continuation for deferred execution.
533  

531  

534  
        If the continuation is backed by a continuation_op
532  
        If the continuation is backed by a continuation_op
535  
        (tagged), posts it directly as a scheduler_op — zero
533  
        (tagged), posts it directly as a scheduler_op — zero
536  
        heap allocation. Otherwise falls back to the
534  
        heap allocation. Otherwise falls back to the
537  
        heap-allocating post(coroutine_handle<>) path.
535  
        heap-allocating post(coroutine_handle<>) path.
538  
    */
536  
    */
539  
    void post(capy::continuation& c) const
537  
    void post(capy::continuation& c) const
540  
    {
538  
    {
541  
        auto* op = detail::continuation_op::try_from_continuation(c);
539  
        auto* op = detail::continuation_op::try_from_continuation(c);
542  
        if (op)
540  
        if (op)
543  
            ctx_->sched_->post(op);
541  
            ctx_->sched_->post(op);
544  
        else
542  
        else
545  
            ctx_->sched_->post(c.h);
543  
            ctx_->sched_->post(c.h);
546  
    }
544  
    }
547  

545  

548  
    /** Post a bare coroutine handle for deferred execution.
546  
    /** Post a bare coroutine handle for deferred execution.
549  

547  

550  
        Heap-allocates a scheduler_op to wrap the handle. Prefer
548  
        Heap-allocates a scheduler_op to wrap the handle. Prefer
551  
        posting through a continuation_op-backed continuation when
549  
        posting through a continuation_op-backed continuation when
552  
        the continuation has suitable lifetime.
550  
        the continuation has suitable lifetime.
553  

551  

554  
        @param h The coroutine handle to post.
552  
        @param h The coroutine handle to post.
555  
    */
553  
    */
556  
    void post(std::coroutine_handle<> h) const
554  
    void post(std::coroutine_handle<> h) const
557  
    {
555  
    {
558  
        ctx_->sched_->post(h);
556  
        ctx_->sched_->post(h);
559  
    }
557  
    }
560  

558  

561  
    /** Compare two executors for equality.
559  
    /** Compare two executors for equality.
562  

560  

563  
        @return `true` if both executors refer to the same context.
561  
        @return `true` if both executors refer to the same context.
564  
    */
562  
    */
565  
    bool operator==(executor_type const& other) const noexcept
563  
    bool operator==(executor_type const& other) const noexcept
566  
    {
564  
    {
567  
        return ctx_ == other.ctx_;
565  
        return ctx_ == other.ctx_;
568  
    }
566  
    }
569  

567  

570  
    /** Compare two executors for inequality.
568  
    /** Compare two executors for inequality.
571  

569  

572  
        @return `true` if the executors refer to different contexts.
570  
        @return `true` if the executors refer to different contexts.
573  
    */
571  
    */
574  
    bool operator!=(executor_type const& other) const noexcept
572  
    bool operator!=(executor_type const& other) const noexcept
575  
    {
573  
    {
576  
        return ctx_ != other.ctx_;
574  
        return ctx_ != other.ctx_;
577  
    }
575  
    }
578  
};
576  
};
579  

577  

580  
inline io_context::executor_type
578  
inline io_context::executor_type
581  
io_context::get_executor() const noexcept
579  
io_context::get_executor() const noexcept
582  
{
580  
{
583  
    return executor_type(const_cast<io_context&>(*this));
581  
    return executor_type(const_cast<io_context&>(*this));
584  
}
582  
}
585  

583  

586  
} // namespace boost::corosio
584  
} // namespace boost::corosio
587  

585  

588  
#endif // BOOST_COROSIO_IO_CONTEXT_HPP
586  
#endif // BOOST_COROSIO_IO_CONTEXT_HPP