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

123  

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

125  

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

129  

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

136  

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

139  

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

145  

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

150  

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

156  

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

159  

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

162  

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

166  

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

170  

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

173  

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

175  

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

180  

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

182  

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

191  

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

193  

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

210  

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

212  

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

234  

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

236  

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

239  

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

241  

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

244  

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

248  

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

250  

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

258  

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

260  

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

268  

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

270  

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

278  

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

280  

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

284  

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

287  

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

294  

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

296  

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

300  

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

303  

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

310  

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

312  

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

316  

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

319  

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

321  

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

329  

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

331  

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

335  

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

338  

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

340  

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

353  

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

355  

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

359  

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

362  

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

364  

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

372  

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

374  

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

378  

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

381  

382  
        @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.
383  

383  

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

396  

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

402  

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

405  

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

410  

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

412  

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

416  

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

419  

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

426  

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

428  

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

432  

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

435  

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

443  

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

445  

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

449  

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

453  

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

461  

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

464  

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

468  

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

470  

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

474  

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

476  

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

483  

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

485  

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

492  

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

494  

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

501  

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

503  

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

511  

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

513  

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

518  

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

521  

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

531  

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

533  

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

547  

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

549  

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

553  

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

560  

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

562  

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

569  

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

571  

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

579  

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

585  

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

587  

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