100.00% Lines (83/83) 100.00% Functions (23/23)
TLA Baseline Branch
Line Hits Code Line Hits Code
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   @note Constructing an `io_context` with `concurrency_hint > 1` 68   @note Constructing an `io_context` with `concurrency_hint > 1`
69   and all three budget fields at their defaults overrides 69   and all three budget fields at their defaults overrides
70   them to disable inline completion (post-everything mode), 70   them to disable inline completion (post-everything mode),
71   since multi-thread workloads benefit from cross-thread 71   since multi-thread workloads benefit from cross-thread
72   work-stealing. Setting any budget field to a non-default 72   work-stealing. Setting any budget field to a non-default
73   value disables the override. 73   value disables the override.
74   */ 74   */
75   unsigned inline_budget_initial = 2; 75   unsigned inline_budget_initial = 2;
76   76  
77   /** Hard ceiling on adaptive inline budget ramp-up. 77   /** Hard ceiling on adaptive inline budget ramp-up.
78   78  
79   The budget doubles each cycle it is fully consumed, up to 79   The budget doubles each cycle it is fully consumed, up to
80   this limit. Applies to reactor backends only. 80   this limit. Applies to reactor backends only.
81   */ 81   */
82   unsigned inline_budget_max = 16; 82   unsigned inline_budget_max = 16;
83   83  
84   /** Inline budget when no other thread assists the reactor. 84   /** Inline budget when no other thread assists the reactor.
85   85  
86   When only one thread is running the event loop, this 86   When only one thread is running the event loop, this
87   value caps the inline budget to preserve fairness. 87   value caps the inline budget to preserve fairness.
88   Applies to reactor backends only. 88   Applies to reactor backends only.
89   */ 89   */
90   unsigned unassisted_budget = 4; 90   unsigned unassisted_budget = 4;
91 - /** Maximum `GetQueuedCompletionStatus` timeout in milliseconds.  
92 -  
93 - Bounds how long the IOCP scheduler blocks between timer  
94 - rechecks. Lower values improve timer responsiveness at the  
95 - cost of more syscalls. Applies to IOCP only.  
96 - */  
97 - unsigned gqcs_timeout_ms = 500;  
98 -  
99   91  
100   /** Thread pool size for blocking I/O (file I/O, DNS resolution). 92   /** Thread pool size for blocking I/O (file I/O, DNS resolution).
101   93  
102   Sets the number of worker threads in the shared thread pool 94   Sets the number of worker threads in the shared thread pool
103   used by POSIX file services and DNS resolution. Must be at 95   used by POSIX file services and DNS resolution. Must be at
104   least 1. Applies to POSIX backends only; ignored on IOCP 96   least 1. Applies to POSIX backends only; ignored on IOCP
105   where file I/O uses native overlapped I/O. 97   where file I/O uses native overlapped I/O.
106   */ 98   */
107   unsigned thread_pool_size = 1; 99   unsigned thread_pool_size = 1;
108   100  
109   /** Enable single-threaded mode (disable scheduler locking). 101   /** Enable single-threaded mode (disable scheduler locking).
110   102  
111   When true, the scheduler skips all mutex lock/unlock and 103   When true, the scheduler skips all mutex lock/unlock and
112   condition variable operations on the hot path. This 104   condition variable operations on the hot path. This
113   eliminates synchronization overhead when only one thread 105   eliminates synchronization overhead when only one thread
114   calls `run()`. 106   calls `run()`.
115   107  
116   @par Restrictions 108   @par Restrictions
117   - Only one thread may call `run()` (or any run variant). 109   - Only one thread may call `run()` (or any run variant).
118   - Posting work from another thread is undefined behavior. 110   - Posting work from another thread is undefined behavior.
119   - DNS resolution returns `operation_not_supported`. 111   - DNS resolution returns `operation_not_supported`.
120   - POSIX file I/O returns `operation_not_supported`. 112   - POSIX file I/O returns `operation_not_supported`.
121   - Signal sets should not be shared across contexts. 113   - Signal sets should not be shared across contexts.
122   114  
123   @note Constructing an `io_context` with `concurrency_hint == 1` 115   @note Constructing an `io_context` with `concurrency_hint == 1`
124   automatically enables single-threaded mode regardless of 116   automatically enables single-threaded mode regardless of
125   this field's value, matching asio's convention. To opt out, 117   this field's value, matching asio's convention. To opt out,
126   pass `concurrency_hint > 1`. 118   pass `concurrency_hint > 1`.
127   */ 119   */
128   bool single_threaded = false; 120   bool single_threaded = false;
129   121  
130   /** Enable IORING_SETUP_SQPOLL on the io_uring backend. 122   /** Enable IORING_SETUP_SQPOLL on the io_uring backend.
131   123  
132   With SQPOLL, the kernel forks a thread that busy-polls the 124   With SQPOLL, the kernel forks a thread that busy-polls the
133   submission ring; submission becomes a userspace-only memory 125   submission ring; submission becomes a userspace-only memory
134   store, eliminating the io_uring_enter syscall on the submit 126   store, eliminating the io_uring_enter syscall on the submit
135   path. Most useful for sustained traffic. Idle thread parks 127   path. Most useful for sustained traffic. Idle thread parks
136   after `sq_thread_idle_ms` of no activity. 128   after `sq_thread_idle_ms` of no activity.
137   129  
138   Independent of `single_threaded`. Default: off. 130   Independent of `single_threaded`. Default: off.
139   131  
140   Ignored on non-io_uring backends. 132   Ignored on non-io_uring backends.
141   */ 133   */
142   bool enable_sqpoll = false; 134   bool enable_sqpoll = false;
143   135  
144   /** SQ-poll idle timeout in milliseconds. 136   /** SQ-poll idle timeout in milliseconds.
145   137  
146   After this many ms of no submissions, the kernel polling 138   After this many ms of no submissions, the kernel polling
147   thread sleeps; next submit re-wakes it via SQ_WAKEUP. 0 139   thread sleeps; next submit re-wakes it via SQ_WAKEUP. 0
148   means use the kernel default (1ms). Recommended for bursty 140   means use the kernel default (1ms). Recommended for bursty
149   workloads: 100-1000ms (avoids park/unpark thrash). 141   workloads: 100-1000ms (avoids park/unpark thrash).
150   142  
151   Ignored unless `enable_sqpoll` is true. Ignored on 143   Ignored unless `enable_sqpoll` is true. Ignored on
152   non-io_uring backends. 144   non-io_uring backends.
153   */ 145   */
154   unsigned sq_thread_idle_ms = 0; 146   unsigned sq_thread_idle_ms = 0;
155   147  
156   /** Pin the SQ-poll kernel thread to this CPU. 148   /** Pin the SQ-poll kernel thread to this CPU.
157   149  
158   -1 means do not pin (kernel scheduler picks). Pinning off 150   -1 means do not pin (kernel scheduler picks). Pinning off
159   the dispatch core is recommended on latency-sensitive 151   the dispatch core is recommended on latency-sensitive
160   deployments to avoid cache contention. 152   deployments to avoid cache contention.
161   153  
162   Ignored unless `enable_sqpoll` is true. Ignored on 154   Ignored unless `enable_sqpoll` is true. Ignored on
163   non-io_uring backends. 155   non-io_uring backends.
164   */ 156   */
165   int sq_thread_cpu = -1; 157   int sq_thread_cpu = -1;
166   }; 158   };
167   159  
168   namespace detail { 160   namespace detail {
169   class timer_service; 161   class timer_service;
170   } // namespace detail 162   } // namespace detail
171   163  
172   /** An I/O context for running asynchronous operations. 164   /** An I/O context for running asynchronous operations.
173   165  
174   The io_context provides an execution environment for async 166   The io_context provides an execution environment for async
175   operations. It maintains a queue of pending work items and 167   operations. It maintains a queue of pending work items and
176   processes them when `run()` is called. 168   processes them when `run()` is called.
177   169  
178   The default and unsigned constructors select the platform's 170   The default and unsigned constructors select the platform's
179   native backend: 171   native backend:
180   - Windows: IOCP 172   - Windows: IOCP
181   - Linux: epoll 173   - Linux: epoll
182   - BSD/macOS: kqueue 174   - BSD/macOS: kqueue
183   - Other POSIX: select 175   - Other POSIX: select
184   176  
185   The template constructor accepts a backend tag value to 177   The template constructor accepts a backend tag value to
186   choose a specific backend at compile time: 178   choose a specific backend at compile time:
187   179  
188   @par Example 180   @par Example
189   @code 181   @code
190   io_context ioc; // platform default 182   io_context ioc; // platform default
191   io_context ioc2(corosio::epoll); // explicit backend 183   io_context ioc2(corosio::epoll); // explicit backend
192   @endcode 184   @endcode
193   185  
194   @par Thread Safety 186   @par Thread Safety
195   Distinct objects: Safe.@n 187   Distinct objects: Safe.@n
196   Shared objects: Safe, if using a concurrency hint greater 188   Shared objects: Safe, if using a concurrency hint greater
197   than 1. 189   than 1.
198   190  
199   @see epoll_t, select_t, kqueue_t, iocp_t 191   @see epoll_t, select_t, kqueue_t, iocp_t
200   */ 192   */
201   class BOOST_COROSIO_DECL io_context : public capy::execution_context 193   class BOOST_COROSIO_DECL io_context : public capy::execution_context
202   { 194   {
203   /// Pre-create services that depend on options (before construct). 195   /// Pre-create services that depend on options (before construct).
204   void apply_options_pre_(io_context_options const& opts); 196   void apply_options_pre_(io_context_options const& opts);
205   197  
206   /// Apply runtime tuning to the scheduler (after construct). 198   /// Apply runtime tuning to the scheduler (after construct).
207   void apply_options_post_( 199   void apply_options_post_(
208   io_context_options const& opts, 200   io_context_options const& opts,
209   unsigned concurrency_hint); 201   unsigned concurrency_hint);
210   202  
211   /// Switch the scheduler to single-threaded (lockless) mode. 203   /// Switch the scheduler to single-threaded (lockless) mode.
212   void configure_single_threaded_(); 204   void configure_single_threaded_();
213   205  
214   protected: 206   protected:
215   detail::scheduler* sched_; 207   detail::scheduler* sched_;
216   208  
217   public: 209   public:
218   /** The executor type for this context. */ 210   /** The executor type for this context. */
219   class executor_type; 211   class executor_type;
220   212  
221   /** Construct with default concurrency and platform backend. 213   /** Construct with default concurrency and platform backend.
222   214  
223   Uses `std::thread::hardware_concurrency()` clamped to a minimum 215   Uses `std::thread::hardware_concurrency()` clamped to a minimum
224   of 2 as the concurrency hint, so the default constructor never 216   of 2 as the concurrency hint, so the default constructor never
225   silently engages single-threaded mode (see 217   silently engages single-threaded mode (see
226   @ref io_context_options::single_threaded). Pass an explicit 218   @ref io_context_options::single_threaded). Pass an explicit
227   `concurrency_hint == 1` to opt into single-threaded mode. 219   `concurrency_hint == 1` to opt into single-threaded mode.
228   */ 220   */
229   io_context(); 221   io_context();
230   222  
231   /** Construct with a concurrency hint and platform backend. 223   /** Construct with a concurrency hint and platform backend.
232   224  
233   @param concurrency_hint Hint for the number of threads 225   @param concurrency_hint Hint for the number of threads
234   that will call `run()`. 226   that will call `run()`.
235   */ 227   */
236   explicit io_context(unsigned concurrency_hint); 228   explicit io_context(unsigned concurrency_hint);
237   229  
238   /** Construct with runtime tuning options and platform backend. 230   /** Construct with runtime tuning options and platform backend.
239   231  
240   @param opts Runtime options controlling scheduler and 232   @param opts Runtime options controlling scheduler and
241   service behavior. 233   service behavior.
242   @param concurrency_hint Hint for the number of threads 234   @param concurrency_hint Hint for the number of threads
243   that will call `run()`. 235   that will call `run()`.
244   */ 236   */
245   explicit io_context( 237   explicit io_context(
246   io_context_options const& opts, 238   io_context_options const& opts,
247   unsigned concurrency_hint = std::thread::hardware_concurrency()); 239   unsigned concurrency_hint = std::thread::hardware_concurrency());
248   240  
249   /** Construct with an explicit backend tag. 241   /** Construct with an explicit backend tag.
250   242  
251   @param backend The backend tag value selecting the I/O 243   @param backend The backend tag value selecting the I/O
252   multiplexer (e.g. `corosio::epoll`). 244   multiplexer (e.g. `corosio::epoll`).
253   @param concurrency_hint Hint for the number of threads 245   @param concurrency_hint Hint for the number of threads
254   that will call `run()`. 246   that will call `run()`.
255   */ 247   */
256   template<class Backend> 248   template<class Backend>
257   requires requires { Backend::construct; } 249   requires requires { Backend::construct; }
HITCBC 258   1176 explicit io_context( 250   1176 explicit io_context(
259   Backend backend, 251   Backend backend,
260   unsigned concurrency_hint = std::thread::hardware_concurrency()) 252   unsigned concurrency_hint = std::thread::hardware_concurrency())
261   : capy::execution_context(this) 253   : capy::execution_context(this)
HITCBC 262   1176 , sched_(nullptr) 254   1176 , sched_(nullptr)
263   { 255   {
264   (void)backend; 256   (void)backend;
HITCBC 265   1176 sched_ = &Backend::construct(*this, concurrency_hint); 257   1176 sched_ = &Backend::construct(*this, concurrency_hint);
HITCBC 266   1176 if (concurrency_hint == 1) 258   1176 if (concurrency_hint == 1)
HITCBC 267   4 configure_single_threaded_(); 259   4 configure_single_threaded_();
HITCBC 268   1176 } 260   1176 }
269   261  
270   /** Construct with an explicit backend tag and runtime options. 262   /** Construct with an explicit backend tag and runtime options.
271   263  
272   @param backend The backend tag value selecting the I/O 264   @param backend The backend tag value selecting the I/O
273   multiplexer (e.g. `corosio::epoll`). 265   multiplexer (e.g. `corosio::epoll`).
274   @param opts Runtime options controlling scheduler and 266   @param opts Runtime options controlling scheduler and
275   service behavior. 267   service behavior.
276   @param concurrency_hint Hint for the number of threads 268   @param concurrency_hint Hint for the number of threads
277   that will call `run()`. 269   that will call `run()`.
278   */ 270   */
279   template<class Backend> 271   template<class Backend>
280   requires requires { Backend::construct; } 272   requires requires { Backend::construct; }
HITCBC 281   12 explicit io_context( 273   12 explicit io_context(
282   Backend backend, 274   Backend backend,
283   io_context_options const& opts, 275   io_context_options const& opts,
284   unsigned concurrency_hint = std::thread::hardware_concurrency()) 276   unsigned concurrency_hint = std::thread::hardware_concurrency())
285   : capy::execution_context(this) 277   : capy::execution_context(this)
HITCBC 286   12 , sched_(nullptr) 278   12 , sched_(nullptr)
287   { 279   {
288   (void)backend; 280   (void)backend;
HITCBC 289   12 apply_options_pre_(opts); 281   12 apply_options_pre_(opts);
HITCBC 290   12 sched_ = &Backend::construct(*this, concurrency_hint); 282   12 sched_ = &Backend::construct(*this, concurrency_hint);
HITCBC 291   12 apply_options_post_(opts, concurrency_hint); 283   12 apply_options_post_(opts, concurrency_hint);
HITCBC 292   12 } 284   12 }
293   285  
294   ~io_context(); 286   ~io_context();
295   287  
296   io_context(io_context const&) = delete; 288   io_context(io_context const&) = delete;
297   io_context& operator=(io_context const&) = delete; 289   io_context& operator=(io_context const&) = delete;
298   290  
299   /** Return an executor for this context. 291   /** Return an executor for this context.
300   292  
301   The returned executor can be used to dispatch coroutines 293   The returned executor can be used to dispatch coroutines
302   and post work items to this context. 294   and post work items to this context.
303   295  
304   @return An executor associated with this context. 296   @return An executor associated with this context.
305   */ 297   */
306   executor_type get_executor() const noexcept; 298   executor_type get_executor() const noexcept;
307   299  
308   /** Signal the context to stop processing. 300   /** Signal the context to stop processing.
309   301  
310   This causes `run()` to return as soon as possible. Any pending 302   This causes `run()` to return as soon as possible. Any pending
311   work items remain queued. 303   work items remain queued.
312   */ 304   */
HITCBC 313   6 void stop() 305   6 void stop()
314   { 306   {
HITCBC 315   6 sched_->stop(); 307   6 sched_->stop();
HITCBC 316   6 } 308   6 }
317   309  
318   /** Return whether the context has been stopped. 310   /** Return whether the context has been stopped.
319   311  
320   @return `true` if `stop()` has been called and `restart()` 312   @return `true` if `stop()` has been called and `restart()`
321   has not been called since. 313   has not been called since.
322   */ 314   */
HITCBC 323   66 bool stopped() const noexcept 315   66 bool stopped() const noexcept
324   { 316   {
HITCBC 325   66 return sched_->stopped(); 317   66 return sched_->stopped();
326   } 318   }
327   319  
328   /** Restart the context after being stopped. 320   /** Restart the context after being stopped.
329   321  
330   This function must be called before `run()` can be called 322   This function must be called before `run()` can be called
331   again after `stop()` has been called. 323   again after `stop()` has been called.
332   */ 324   */
HITCBC 333   175 void restart() 325   175 void restart()
334   { 326   {
HITCBC 335   175 sched_->restart(); 327   175 sched_->restart();
HITCBC 336   175 } 328   175 }
337   329  
338   /** Process all pending work items. 330   /** Process all pending work items.
339   331  
340   This function blocks until all pending work items have been 332   This function blocks until all pending work items have been
341   executed or `stop()` is called. The context is stopped 333   executed or `stop()` is called. The context is stopped
342   when there is no more outstanding work. 334   when there is no more outstanding work.
343   335  
344   @note The context must be restarted with `restart()` before 336   @note The context must be restarted with `restart()` before
345   calling this function again after it returns. 337   calling this function again after it returns.
346   338  
347   @return The number of handlers executed. 339   @return The number of handlers executed.
348   */ 340   */
HITCBC 349   833 std::size_t run() 341   833 std::size_t run()
350   { 342   {
HITCBC 351   833 return sched_->run(); 343   833 return sched_->run();
352   } 344   }
353   345  
354   /** Process at most one pending work item. 346   /** Process at most one pending work item.
355   347  
356   This function blocks until one work item has been executed 348   This function blocks until one work item has been executed
357   or `stop()` is called. The context is stopped when there 349   or `stop()` is called. The context is stopped when there
358   is no more outstanding work. 350   is no more outstanding work.
359   351  
360   @note The context must be restarted with `restart()` before 352   @note The context must be restarted with `restart()` before
361   calling this function again after it returns. 353   calling this function again after it returns.
362   354  
363   @return The number of handlers executed (0 or 1). 355   @return The number of handlers executed (0 or 1).
364   */ 356   */
HITCBC 365   16 std::size_t run_one() 357   16 std::size_t run_one()
366   { 358   {
HITCBC 367   16 return sched_->run_one(); 359   16 return sched_->run_one();
368   } 360   }
369   361  
370   /** Process work items for the specified duration. 362   /** Process work items for the specified duration.
371   363  
372   This function blocks until work items have been executed for 364   This function blocks until work items have been executed for
373   the specified duration, or `stop()` is called. The context 365   the specified duration, or `stop()` is called. The context
374   is stopped when there is no more outstanding work. 366   is stopped when there is no more outstanding work.
375   367  
376   @note The context must be restarted with `restart()` before 368   @note The context must be restarted with `restart()` before
377   calling this function again after it returns. 369   calling this function again after it returns.
378   370  
379   @param rel_time The duration for which to process work. 371   @param rel_time The duration for which to process work.
380   372  
381   @return The number of handlers executed. 373   @return The number of handlers executed.
382   */ 374   */
383   template<class Rep, class Period> 375   template<class Rep, class Period>
HITCBC 384   10 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time) 376   10 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
385   { 377   {
HITCBC 386   10 return run_until(std::chrono::steady_clock::now() + rel_time); 378   10 return run_until(std::chrono::steady_clock::now() + rel_time);
387   } 379   }
388   380  
389   /** Process work items until the specified time. 381   /** Process work items until the specified time.
390   382  
391   This function blocks until the specified time is reached 383   This function blocks until the specified time is reached
392   or `stop()` is called. The context is stopped when there 384   or `stop()` is called. The context is stopped when there
393   is no more outstanding work. 385   is no more outstanding work.
394   386  
395   @note The context must be restarted with `restart()` before 387   @note The context must be restarted with `restart()` before
396   calling this function again after it returns. 388   calling this function again after it returns.
397   389  
398   @param abs_time The time point until which to process work. 390   @param abs_time The time point until which to process work.
399   391  
400   @return The number of handlers executed. 392   @return The number of handlers executed.
401   */ 393   */
402   template<class Clock, class Duration> 394   template<class Clock, class Duration>
403   std::size_t 395   std::size_t
HITCBC 404   10 run_until(std::chrono::time_point<Clock, Duration> const& abs_time) 396   10 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
405   { 397   {
HITCBC 406   10 std::size_t n = 0; 398   10 std::size_t n = 0;
HITCBC 407   28 while (run_one_until(abs_time)) 399   28 while (run_one_until(abs_time))
HITCBC 408   18 if (n != (std::numeric_limits<std::size_t>::max)()) 400   18 if (n != (std::numeric_limits<std::size_t>::max)())
HITCBC 409   18 ++n; 401   18 ++n;
HITCBC 410   10 return n; 402   10 return n;
411   } 403   }
412   404  
413   /** Process at most one work item for the specified duration. 405   /** Process at most one work item for the specified duration.
414   406  
415   This function blocks until one work item has been executed, 407   This function blocks until one work item has been executed,
416   the specified duration has elapsed, or `stop()` is called. 408   the specified duration has elapsed, or `stop()` is called.
417   The context is stopped when there is no more outstanding work. 409   The context is stopped when there is no more outstanding work.
418   410  
419   @note The context must be restarted with `restart()` before 411   @note The context must be restarted with `restart()` before
420   calling this function again after it returns. 412   calling this function again after it returns.
421   413  
422   @param rel_time The duration for which the call may block. 414   @param rel_time The duration for which the call may block.
423   415  
424   @return The number of handlers executed (0 or 1). 416   @return The number of handlers executed (0 or 1).
425   */ 417   */
426   template<class Rep, class Period> 418   template<class Rep, class Period>
HITCBC 427   6 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time) 419   6 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
428   { 420   {
HITCBC 429   6 return run_one_until(std::chrono::steady_clock::now() + rel_time); 421   6 return run_one_until(std::chrono::steady_clock::now() + rel_time);
430   } 422   }
431   423  
432   /** Process at most one work item until the specified time. 424   /** Process at most one work item until the specified time.
433   425  
434   This function blocks until one work item has been executed, 426   This function blocks until one work item has been executed,
435   the specified time is reached, or `stop()` is called. 427   the specified time is reached, or `stop()` is called.
436   The context is stopped when there is no more outstanding work. 428   The context is stopped when there is no more outstanding work.
437   429  
438   @note The context must be restarted with `restart()` before 430   @note The context must be restarted with `restart()` before
439   calling this function again after it returns. 431   calling this function again after it returns.
440   432  
441   @param abs_time The time point until which the call may block. 433   @param abs_time The time point until which the call may block.
442   434  
443   @return The number of handlers executed (0 or 1). 435   @return The number of handlers executed (0 or 1).
444   */ 436   */
445   template<class Clock, class Duration> 437   template<class Clock, class Duration>
446   std::size_t 438   std::size_t
HITCBC 447   42 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time) 439   42 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
448   { 440   {
HITCBC 449   42 typename Clock::time_point now = Clock::now(); 441   42 typename Clock::time_point now = Clock::now();
HITCBC 450   8 for (;;) 442   8 for (;;)
451   { 443   {
HITCBC 452   50 auto rel_time = abs_time - now; 444   50 auto rel_time = abs_time - now;
453   using rel_type = decltype(rel_time); 445   using rel_type = decltype(rel_time);
HITCBC 454   50 if (rel_time < rel_type::zero()) 446   50 if (rel_time < rel_type::zero())
HITCBC 455   4 rel_time = rel_type::zero(); 447   4 rel_time = rel_type::zero();
HITCBC 456   46 else if (rel_time > std::chrono::seconds(1)) 448   46 else if (rel_time > std::chrono::seconds(1))
HITCBC 457   22 rel_time = std::chrono::seconds(1); 449   22 rel_time = std::chrono::seconds(1);
458   450  
HITCBC 459   50 std::size_t s = sched_->wait_one( 451   50 std::size_t s = sched_->wait_one(
460   static_cast<long>( 452   static_cast<long>(
HITCBC 461   50 std::chrono::duration_cast<std::chrono::microseconds>( 453   50 std::chrono::duration_cast<std::chrono::microseconds>(
462   rel_time) 454   rel_time)
HITCBC 463   50 .count())); 455   50 .count()));
464   456  
HITCBC 465   50 if (s || stopped()) 457   50 if (s || stopped())
HITCBC 466   42 return s; 458   42 return s;
467   459  
HITCBC 468   12 now = Clock::now(); 460   12 now = Clock::now();
HITCBC 469   12 if (now >= abs_time) 461   12 if (now >= abs_time)
HITCBC 470   4 return 0; 462   4 return 0;
471   } 463   }
472   } 464   }
473   465  
474   /** Process all ready work items without blocking. 466   /** Process all ready work items without blocking.
475   467  
476   This function executes all work items that are ready to run 468   This function executes all work items that are ready to run
477   without blocking for more work. The context is stopped 469   without blocking for more work. The context is stopped
478   when there is no more outstanding work. 470   when there is no more outstanding work.
479   471  
480   @note The context must be restarted with `restart()` before 472   @note The context must be restarted with `restart()` before
481   calling this function again after it returns. 473   calling this function again after it returns.
482   474  
483   @return The number of handlers executed. 475   @return The number of handlers executed.
484   */ 476   */
HITCBC 485   26 std::size_t poll() 477   26 std::size_t poll()
486   { 478   {
HITCBC 487   26 return sched_->poll(); 479   26 return sched_->poll();
488   } 480   }
489   481  
490   /** Process at most one ready work item without blocking. 482   /** Process at most one ready work item without blocking.
491   483  
492   This function executes at most one work item that is ready 484   This function executes at most one work item that is ready
493   to run without blocking for more work. The context is 485   to run without blocking for more work. The context is
494   stopped when there is no more outstanding work. 486   stopped when there is no more outstanding work.
495   487  
496   @note The context must be restarted with `restart()` before 488   @note The context must be restarted with `restart()` before
497   calling this function again after it returns. 489   calling this function again after it returns.
498   490  
499   @return The number of handlers executed (0 or 1). 491   @return The number of handlers executed (0 or 1).
500   */ 492   */
HITCBC 501   8 std::size_t poll_one() 493   8 std::size_t poll_one()
502   { 494   {
HITCBC 503   8 return sched_->poll_one(); 495   8 return sched_->poll_one();
504   } 496   }
505   }; 497   };
506   498  
507   /** An executor for dispatching work to an I/O context. 499   /** An executor for dispatching work to an I/O context.
508   500  
509   The executor provides the interface for posting work items and 501   The executor provides the interface for posting work items and
510   dispatching coroutines to the associated context. It satisfies 502   dispatching coroutines to the associated context. It satisfies
511   the `capy::Executor` concept. 503   the `capy::Executor` concept.
512   504  
513   Executors are lightweight handles that can be copied and compared 505   Executors are lightweight handles that can be copied and compared
514   for equality. Two executors compare equal if they refer to the 506   for equality. Two executors compare equal if they refer to the
515   same context. 507   same context.
516   508  
517   @par Thread Safety 509   @par Thread Safety
518   Distinct objects: Safe.@n 510   Distinct objects: Safe.@n
519   Shared objects: Safe. 511   Shared objects: Safe.
520   */ 512   */
521   class io_context::executor_type 513   class io_context::executor_type
522   { 514   {
523   io_context* ctx_ = nullptr; 515   io_context* ctx_ = nullptr;
524   516  
525   public: 517   public:
526   /** Default constructor. 518   /** Default constructor.
527   519  
528   Constructs an executor not associated with any context. 520   Constructs an executor not associated with any context.
529   */ 521   */
530   executor_type() = default; 522   executor_type() = default;
531   523  
532   /** Construct an executor from a context. 524   /** Construct an executor from a context.
533   525  
534   @param ctx The context to associate with this executor. 526   @param ctx The context to associate with this executor.
535   */ 527   */
HITCBC 536   1248 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {} 528   1248 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
537   529  
538   /** Return a reference to the associated execution context. 530   /** Return a reference to the associated execution context.
539   531  
540   @return Reference to the context. 532   @return Reference to the context.
541   */ 533   */
HITCBC 542   2101 io_context& context() const noexcept 534   2097 io_context& context() const noexcept
543   { 535   {
HITCBC 544   2101 return *ctx_; 536   2097 return *ctx_;
545   } 537   }
546   538  
547   /** Check if the current thread is running this executor's context. 539   /** Check if the current thread is running this executor's context.
548   540  
549   @return `true` if `run()` is being called on this thread. 541   @return `true` if `run()` is being called on this thread.
550   */ 542   */
HITCBC 551   2156 bool running_in_this_thread() const noexcept 543   2152 bool running_in_this_thread() const noexcept
552   { 544   {
HITCBC 553   2156 return ctx_->sched_->running_in_this_thread(); 545   2152 return ctx_->sched_->running_in_this_thread();
554   } 546   }
555   547  
556   /** Informs the executor that work is beginning. 548   /** Informs the executor that work is beginning.
557   549  
558   Must be paired with `on_work_finished()`. 550   Must be paired with `on_work_finished()`.
559   */ 551   */
HITCBC 560   2437 void on_work_started() const noexcept 552   2433 void on_work_started() const noexcept
561   { 553   {
HITCBC 562   2437 ctx_->sched_->work_started(); 554   2433 ctx_->sched_->work_started();
HITCBC 563   2437 } 555   2433 }
564   556  
565   /** Informs the executor that work has completed. 557   /** Informs the executor that work has completed.
566   558  
567   @par Preconditions 559   @par Preconditions
568   A preceding call to `on_work_started()` on an equal executor. 560   A preceding call to `on_work_started()` on an equal executor.
569   */ 561   */
HITCBC 570   2392 void on_work_finished() const noexcept 562   2388 void on_work_finished() const noexcept
571   { 563   {
HITCBC 572   2392 ctx_->sched_->work_finished(); 564   2388 ctx_->sched_->work_finished();
HITCBC 573   2392 } 565   2388 }
574   566  
575   /** Dispatch a continuation. 567   /** Dispatch a continuation.
576   568  
577   Returns a handle for symmetric transfer. If called from 569   Returns a handle for symmetric transfer. If called from
578   within `run()`, returns `c.h`. Otherwise posts the 570   within `run()`, returns `c.h`. Otherwise posts the
579   enclosing continuation_op as a scheduler_op for later 571   enclosing continuation_op as a scheduler_op for later
580   execution and returns `std::noop_coroutine()`. 572   execution and returns `std::noop_coroutine()`.
581   573  
582   @param c The continuation to dispatch. Must be the `cont` 574   @param c The continuation to dispatch. Must be the `cont`
583   member of a `detail::continuation_op`. 575   member of a `detail::continuation_op`.
584   576  
585   @return A handle for symmetric transfer or `std::noop_coroutine()`. 577   @return A handle for symmetric transfer or `std::noop_coroutine()`.
586   */ 578   */
HITCBC 587   2152 std::coroutine_handle<> dispatch(capy::continuation& c) const 579   2148 std::coroutine_handle<> dispatch(capy::continuation& c) const
588   { 580   {
HITCBC 589   2152 if (running_in_this_thread()) 581   2148 if (running_in_this_thread())
HITCBC 590   679 return c.h; 582   675 return c.h;
HITCBC 591   1473 post(c); 583   1473 post(c);
HITCBC 592   1473 return std::noop_coroutine(); 584   1473 return std::noop_coroutine();
593   } 585   }
594   586  
595   /** Post a continuation for deferred execution. 587   /** Post a continuation for deferred execution.
596   588  
597   If the continuation is backed by a continuation_op 589   If the continuation is backed by a continuation_op
598   (tagged), posts it directly as a scheduler_op — zero 590   (tagged), posts it directly as a scheduler_op — zero
599   heap allocation. Otherwise falls back to the 591   heap allocation. Otherwise falls back to the
600   heap-allocating post(coroutine_handle<>) path. 592   heap-allocating post(coroutine_handle<>) path.
601   */ 593   */
HITCBC 602   10552 void post(capy::continuation& c) const 594   10443 void post(capy::continuation& c) const
603   { 595   {
HITCBC 604   10552 auto* op = detail::continuation_op::try_from_continuation(c); 596   10443 auto* op = detail::continuation_op::try_from_continuation(c);
HITCBC 605   10552 if (op) 597   10443 if (op)
HITCBC 606   9073 ctx_->sched_->post(op); 598   8964 ctx_->sched_->post(op);
607   else 599   else
HITCBC 608   1479 ctx_->sched_->post(c.h); 600   1479 ctx_->sched_->post(c.h);
HITCBC 609   10552 } 601   10443 }
610   602  
611   /** Post a bare coroutine handle for deferred execution. 603   /** Post a bare coroutine handle for deferred execution.
612   604  
613   Heap-allocates a scheduler_op to wrap the handle. Prefer 605   Heap-allocates a scheduler_op to wrap the handle. Prefer
614   posting through a continuation_op-backed continuation when 606   posting through a continuation_op-backed continuation when
615   the continuation has suitable lifetime. 607   the continuation has suitable lifetime.
616   608  
617   @param h The coroutine handle to post. 609   @param h The coroutine handle to post.
618   */ 610   */
HITCBC 619   2876 void post(std::coroutine_handle<> h) const 611   2876 void post(std::coroutine_handle<> h) const
620   { 612   {
HITCBC 621   2876 ctx_->sched_->post(h); 613   2876 ctx_->sched_->post(h);
HITCBC 622   2876 } 614   2876 }
623   615  
624   /** Compare two executors for equality. 616   /** Compare two executors for equality.
625   617  
626   @return `true` if both executors refer to the same context. 618   @return `true` if both executors refer to the same context.
627   */ 619   */
HITCBC 628   2 bool operator==(executor_type const& other) const noexcept 620   2 bool operator==(executor_type const& other) const noexcept
629   { 621   {
HITCBC 630   2 return ctx_ == other.ctx_; 622   2 return ctx_ == other.ctx_;
631   } 623   }
632   624  
633   /** Compare two executors for inequality. 625   /** Compare two executors for inequality.
634   626  
635   @return `true` if the executors refer to different contexts. 627   @return `true` if the executors refer to different contexts.
636   */ 628   */
637   bool operator!=(executor_type const& other) const noexcept 629   bool operator!=(executor_type const& other) const noexcept
638   { 630   {
639   return ctx_ != other.ctx_; 631   return ctx_ != other.ctx_;
640   } 632   }
641   }; 633   };
642   634  
643   inline io_context::executor_type 635   inline io_context::executor_type
HITCBC 644   1248 io_context::get_executor() const noexcept 636   1248 io_context::get_executor() const noexcept
645   { 637   {
HITCBC 646   1248 return executor_type(const_cast<io_context&>(*this)); 638   1248 return executor_type(const_cast<io_context&>(*this));
647   } 639   }
648   640  
649   } // namespace boost::corosio 641   } // namespace boost::corosio
650   642  
651   #endif // BOOST_COROSIO_IO_CONTEXT_HPP 643   #endif // BOOST_COROSIO_IO_CONTEXT_HPP