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  
namespace detail {
119  
namespace detail {
120  
struct timer_service_access;
120  
struct timer_service_access;
121  
} // namespace detail
121  
} // namespace detail
122  

122  

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

124  

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

128  

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

135  

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

138  

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

144  

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

149  

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

155  

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

158  

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

161  

162  
protected:
162  
protected:
163  
    detail::scheduler* sched_;
163  
    detail::scheduler* sched_;
164  

164  

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

168  

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

171  

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

173  

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

178  

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

180  

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

189  

190  
    /** Construct with an explicit backend tag.
190  
    /** Construct with an explicit backend tag.
191  

191  

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

208  

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

210  

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

232  

233  
    ~io_context();
233  
    ~io_context();
234  

234  

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

237  

238  
    /** Return an executor for this context.
238  
    /** Return an executor for this context.
239  

239  

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

242  

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

246  

247  
    /** Signal the context to stop processing.
247  
    /** Signal the context to stop processing.
248  

248  

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

256  

257  
    /** Return whether the context has been stopped.
257  
    /** Return whether the context has been stopped.
258  

258  

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

266  

267  
    /** Restart the context after being stopped.
267  
    /** Restart the context after being stopped.
268  

268  

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

276  

277  
    /** Process all pending work items.
277  
    /** Process all pending work items.
278  

278  

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

282  

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

285  

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

292  

293  
    /** Process at most one pending work item.
293  
    /** Process at most one pending work item.
294  

294  

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

298  

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

301  

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

308  

309  
    /** Process work items for the specified duration.
309  
    /** Process work items for the specified duration.
310  

310  

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

314  

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

317  

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

319  

320  
        @return The number of handlers executed.
320  
        @return The number of handlers executed.
321  
    */
321  
    */
322  
    template<class Rep, class Period>
322  
    template<class Rep, class Period>
323  
    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)
324  
    {
324  
    {
325  
        return run_until(std::chrono::steady_clock::now() + rel_time);
325  
        return run_until(std::chrono::steady_clock::now() + rel_time);
326  
    }
326  
    }
327  

327  

328  
    /** Process work items until the specified time.
328  
    /** Process work items until the specified time.
329  

329  

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

333  

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

336  

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

338  

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

351  

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

353  

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

357  

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

360  

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

362  

363  
        @return The number of handlers executed (0 or 1).
363  
        @return The number of handlers executed (0 or 1).
364  
    */
364  
    */
365  
    template<class Rep, class Period>
365  
    template<class Rep, class Period>
366  
    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)
367  
    {
367  
    {
368  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
368  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
369  
    }
369  
    }
370  

370  

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

372  

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

376  

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

379  

380  
        @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.
381  

381  

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

394  

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

400  

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

403  

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

408  

409  
    /** Process all ready work items without blocking.
409  
    /** Process all ready work items without blocking.
410  

410  

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

414  

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

417  

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

424  

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

426  

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

430  

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

433  

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

441  

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

443  

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

447  

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

451  

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

459  

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

462  

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

466  

467  
    /** Construct an executor from a context.
467  
    /** Construct an executor from a context.
468  

468  

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

472  

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

474  

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

481  

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

483  

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

490  

491  
    /** Informs the executor that work is beginning.
491  
    /** Informs the executor that work is beginning.
492  

492  

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

499  

500  
    /** Informs the executor that work has completed.
500  
    /** Informs the executor that work has completed.
501  

501  

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

509  

510  
    /** Dispatch a continuation.
510  
    /** Dispatch a continuation.
511  

511  

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

516  

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

519  

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

529  

530  
    /** Post a continuation for deferred execution.
530  
    /** Post a continuation for deferred execution.
531  

531  

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

545  

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

547  

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

551  

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

558  

559  
    /** Compare two executors for equality.
559  
    /** Compare two executors for equality.
560  

560  

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

567  

568  
    /** Compare two executors for inequality.
568  
    /** Compare two executors for inequality.
569  

569  

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

577  

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

583  

584  
} // namespace boost::corosio
584  
} // namespace boost::corosio
585  

585  

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