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 +
class timer_service;
120  
struct timer_service_access;
121  
struct timer_service_access;
121  
} // namespace detail
122  
} // namespace detail
122  

123  

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

125  

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

129  

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

136  

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

139  

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

145  

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

150  

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

156  

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

159  

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

162  

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

166  

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

170  

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

173  

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

175  

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

180  

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

182  

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

191  

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

193  

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

210  

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

212  

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

234  

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

236  

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

239  

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

241  

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

244  

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

248  

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

250  

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

258  

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

260  

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

268  

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

270  

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

278  

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

280  

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

284  

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

287  

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

294  

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

296  

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

300  

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

303  

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

310  

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

312  

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

316  

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

319  

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

321  

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

329  

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

331  

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

335  

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

338  

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

340  

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

353  

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

355  

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

359  

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

362  

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

364  

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

372  

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

374  

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

378  

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

381  

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

383  

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

396  

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

402  

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

405  

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

410  

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

412  

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

416  

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

419  

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

426  

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

428  

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

432  

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

435  

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

443  

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

445  

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

449  

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

453  

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

461  

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

464  

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

468  

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

470  

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

474  

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

476  

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

483  

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

485  

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

492  

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

494  

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

501  

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

503  

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

511  

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

513  

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

518  

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

521  

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

531  

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

533  

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

547  

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

549  

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

553  

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

560  

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

562  

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

569  

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

571  

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

579  

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

585  

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

587  

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