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