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.
 
31 +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

122  

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

124  

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

128  

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

135  

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

138  

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

144  

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

149  

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

155  

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

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

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

164  

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

168  

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

171  

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

173  

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

178  

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

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

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

191  

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

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

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

232  

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

234  

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

237  

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

239  

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

242  

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

246  

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

248  

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

256  

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

258  

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

266  

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

268  

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

276  

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

278  

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

282  

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

285  

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

292  

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

294  

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

298  

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

301  

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

308  

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

310  

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

314  

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

317  

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

319  

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

327  

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

329  

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

333  

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

336  

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

338  

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

351  

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

353  

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

357  

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

360  

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

362  

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

370  

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

372  

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

376  

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

379  

250  
        @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.
251  

381  

252  
        @return The number of handlers executed (0 or 1).
382  
        @return The number of handlers executed (0 or 1).
253  
    */
383  
    */
254  
    template<class Clock, class Duration>
384  
    template<class Clock, class Duration>
255  
    std::size_t
385  
    std::size_t
256  
    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)
257  
    {
387  
    {
258  
        typename Clock::time_point now = Clock::now();
388  
        typename Clock::time_point now = Clock::now();
259  
        while (now < abs_time)
389  
        while (now < abs_time)
260  
        {
390  
        {
261  
            auto rel_time = abs_time - now;
391  
            auto rel_time = abs_time - now;
262  
            if (rel_time > std::chrono::seconds(1))
392  
            if (rel_time > std::chrono::seconds(1))
263  
                rel_time = std::chrono::seconds(1);
393  
                rel_time = std::chrono::seconds(1);
264  

394  

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

400  

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

403  

274  
            now = Clock::now();
404  
            now = Clock::now();
275  
        }
405  
        }
276  
        return 0;
406  
        return 0;
277  
    }
407  
    }
278  

408  

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

410  

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

414  

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

417  

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

424  

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

426  

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

430  

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

433  

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

441  

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

443  

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

447  

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

451  

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

459  

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

462  

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

466  

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

468  

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

472  

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

474  

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

481  

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

483  

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

490  

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

492  

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

499  

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

501  

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

509  

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

511  

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

516  

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

519  

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

529  

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

531  

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

545  

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

547  

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

551  

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

558  

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

560  

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

567  

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

569  

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

577  

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

583  

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

585  

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