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