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