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