100.00% Lines (83/83) 100.00% Functions (25/25)
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/platform.hpp> 16   #include <boost/corosio/detail/platform.hpp>
17   #include <boost/corosio/detail/scheduler.hpp> 17   #include <boost/corosio/detail/scheduler.hpp>
18   #include <boost/capy/continuation.hpp> 18   #include <boost/capy/continuation.hpp>
19   #include <boost/capy/ex/execution_context.hpp> 19   #include <boost/capy/ex/execution_context.hpp>
20   20  
21   #include <chrono> 21   #include <chrono>
22   #include <coroutine> 22   #include <coroutine>
23   #include <cstddef> 23   #include <cstddef>
24   #include <limits> 24   #include <limits>
25   #include <thread> 25   #include <thread>
26   26  
27   namespace boost::corosio { 27   namespace boost::corosio {
28   28  
29   /** Locking-safety tier for an @ref io_context. 29   /** Locking-safety tier for an @ref io_context.
30   30  
31   Selects which internal locks the scheduler and reactor elide, trading 31   Selects which internal locks the scheduler and reactor elide, trading
32   thread-safety guarantees for reduced synchronization overhead. This is 32   thread-safety guarantees for reduced synchronization overhead. This is
33   the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE` concurrency 33   the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE` concurrency
34   hint constants. The tier is chosen explicitly, not derived from the 34   hint constants. The tier is chosen explicitly, not derived from the
35   `concurrency_hint`. (The reverse does apply: a lockless tier reduces the 35   `concurrency_hint`. (The reverse does apply: a lockless tier reduces the
36   effective hint used for performance tuning to 1.) 36   effective hint used for performance tuning to 1.)
37   37  
38   @see io_context_options::locking 38   @see io_context_options::locking
39   */ 39   */
40   enum class locking_mode 40   enum class locking_mode
41   { 41   {
42   /** Full thread safety (default). All locks enabled; equivalent to 42   /** Full thread safety (default). All locks enabled; equivalent to
43   Boost.Asio's `SAFE`/`DEFAULT`. Any thread may use the context. */ 43   Boost.Asio's `SAFE`/`DEFAULT`. Any thread may use the context. */
44   safe, 44   safe,
45   45  
46   /** Disable only the per-descriptor I/O locks; keep scheduler locking. 46   /** Disable only the per-descriptor I/O locks; keep scheduler locking.
47   Equivalent to Boost.Asio's `UNSAFE_IO`. The context must be run 47   Equivalent to Boost.Asio's `UNSAFE_IO`. The context must be run
48   and driven by a single thread, but resolver and POSIX file 48   and driven by a single thread, but resolver and POSIX file
49   services remain available (they rely on scheduler locking, which 49   services remain available (they rely on scheduler locking, which
50   stays on). */ 50   stays on). */
51   unsafe_io, 51   unsafe_io,
52   52  
53   /** Disable all locking (fully lockless). Equivalent to Boost.Asio's 53   /** Disable all locking (fully lockless). Equivalent to Boost.Asio's
54   `UNSAFE`. 54   `UNSAFE`.
55   55  
56   @par Restrictions 56   @par Restrictions
57   - Only one thread may call `run()` (or any run variant). 57   - Only one thread may call `run()` (or any run variant).
58   - Posting work from another thread is undefined behavior. 58   - Posting work from another thread is undefined behavior.
59   - DNS resolution returns `operation_not_supported`. 59   - DNS resolution returns `operation_not_supported`.
60   - POSIX file I/O returns `operation_not_supported`. 60   - POSIX file I/O returns `operation_not_supported`.
61   - Signal sets should not be shared across contexts. */ 61   - Signal sets should not be shared across contexts. */
62   unsafe 62   unsafe
63   }; 63   };
64   64  
65   /** Runtime tuning options for @ref io_context. 65   /** Runtime tuning options for @ref io_context.
66   66  
67   All fields have defaults that match the library's built-in 67   All fields have defaults that match the library's built-in
68   values, so constructing a default `io_context_options` produces 68   values, so constructing a default `io_context_options` produces
69   identical behavior to an unconfigured context. 69   identical behavior to an unconfigured context.
70   70  
71   Options that apply only to a specific backend family are 71   Options that apply only to a specific backend family are
72   silently ignored when the active backend does not support them. 72   silently ignored when the active backend does not support them.
73   73  
74   @par Example 74   @par Example
75   @code 75   @code
76   io_context_options opts; 76   io_context_options opts;
77   opts.max_events_per_poll = 256; // larger batch per syscall 77   opts.max_events_per_poll = 256; // larger batch per syscall
78   opts.inline_budget_max = 32; // more speculative completions 78   opts.inline_budget_max = 32; // more speculative completions
79   opts.thread_pool_size = 4; // more file-I/O workers 79   opts.thread_pool_size = 4; // more file-I/O workers
80   80  
81   io_context ioc(opts); 81   io_context ioc(opts);
82   @endcode 82   @endcode
83   83  
84   @see io_context, native_io_context 84   @see io_context, native_io_context
85   */ 85   */
86   struct io_context_options 86   struct io_context_options
87   { 87   {
88   /** Maximum events fetched per reactor poll call. 88   /** Maximum events fetched per reactor poll call.
89   89  
90   Controls the buffer size passed to `epoll_wait()` or 90   Controls the buffer size passed to `epoll_wait()` or
91   `kevent()`. Larger values reduce syscall frequency under 91   `kevent()`. Larger values reduce syscall frequency under
92   high load; smaller values improve fairness between 92   high load; smaller values improve fairness between
93   connections. Ignored on IOCP and select backends. 93   connections. Ignored on IOCP and select backends.
94   */ 94   */
95   unsigned max_events_per_poll = 128; 95   unsigned max_events_per_poll = 128;
96   96  
97   /** Starting inline completion budget per handler chain. 97   /** Starting inline completion budget per handler chain.
98   98  
99   After a posted handler executes, the reactor grants this 99   After a posted handler executes, the reactor grants this
100   many speculative inline completions before forcing a 100   many speculative inline completions before forcing a
101   re-queue. Applies to reactor backends only. 101   re-queue. Applies to reactor backends only.
102   102  
103   @note Constructing an `io_context` with `concurrency_hint > 1` 103   @note Constructing an `io_context` with `concurrency_hint > 1`
104   and all three budget fields at their defaults overrides 104   and all three budget fields at their defaults overrides
105   them to disable inline completion (post-everything mode), 105   them to disable inline completion (post-everything mode),
106   since multi-thread workloads benefit from cross-thread 106   since multi-thread workloads benefit from cross-thread
107   work-stealing. Setting any budget field to a non-default 107   work-stealing. Setting any budget field to a non-default
108   value disables the override. 108   value disables the override.
109   */ 109   */
110   unsigned inline_budget_initial = 2; 110   unsigned inline_budget_initial = 2;
111   111  
112   /** Hard ceiling on adaptive inline budget ramp-up. 112   /** Hard ceiling on adaptive inline budget ramp-up.
113   113  
114   The budget doubles each cycle it is fully consumed, up to 114   The budget doubles each cycle it is fully consumed, up to
115   this limit. Applies to reactor backends only. 115   this limit. Applies to reactor backends only.
116   */ 116   */
117   unsigned inline_budget_max = 16; 117   unsigned inline_budget_max = 16;
118   118  
119   /** Inline budget when no other thread assists the reactor. 119   /** Inline budget when no other thread assists the reactor.
120   120  
121   When only one thread is running the event loop, this 121   When only one thread is running the event loop, this
122   value caps the inline budget to preserve fairness. 122   value caps the inline budget to preserve fairness.
123   Applies to reactor backends only. 123   Applies to reactor backends only.
124   */ 124   */
125   unsigned unassisted_budget = 4; 125   unsigned unassisted_budget = 4;
126   126  
127   /** Thread pool size for blocking I/O (file I/O, DNS resolution). 127   /** Thread pool size for blocking I/O (file I/O, DNS resolution).
128   128  
129   Sets the number of worker threads in the shared thread pool 129   Sets the number of worker threads in the shared thread pool
130   used by POSIX file services and DNS resolution. Must be at 130   used by POSIX file services and DNS resolution. Must be at
131   least 1. Applies to POSIX backends only; ignored on IOCP 131   least 1. Applies to POSIX backends only; ignored on IOCP
132   where file I/O uses native overlapped I/O. 132   where file I/O uses native overlapped I/O.
133   */ 133   */
134   unsigned thread_pool_size = 1; 134   unsigned thread_pool_size = 1;
135   135  
136   /** Thread-safety tier. See @ref locking_mode for the tiers and their 136   /** Thread-safety tier. See @ref locking_mode for the tiers and their
137   restrictions. 137   restrictions.
138   */ 138   */
139   locking_mode locking = locking_mode::safe; 139   locking_mode locking = locking_mode::safe;
140   140  
141   /** Enable IORING_SETUP_SQPOLL on the io_uring backend. 141   /** Enable IORING_SETUP_SQPOLL on the io_uring backend.
142   142  
143   With SQPOLL, the kernel forks a thread that busy-polls the 143   With SQPOLL, the kernel forks a thread that busy-polls the
144   submission ring; submission becomes a userspace-only memory 144   submission ring; submission becomes a userspace-only memory
145   store, eliminating the io_uring_enter syscall on the submit 145   store, eliminating the io_uring_enter syscall on the submit
146   path. Most useful for sustained traffic. Idle thread parks 146   path. Most useful for sustained traffic. Idle thread parks
147   after `sq_thread_idle_ms` of no activity. 147   after `sq_thread_idle_ms` of no activity.
148   148  
149   Independent of `locking`. Default: off. 149   Independent of `locking`. Default: off.
150   150  
151   Ignored on non-io_uring backends. 151   Ignored on non-io_uring backends.
152   */ 152   */
153   bool enable_sqpoll = false; 153   bool enable_sqpoll = false;
154   154  
155   /** SQ-poll idle timeout in milliseconds. 155   /** SQ-poll idle timeout in milliseconds.
156   156  
157   After this many ms of no submissions, the kernel polling 157   After this many ms of no submissions, the kernel polling
158   thread sleeps; next submit re-wakes it via SQ_WAKEUP. 0 158   thread sleeps; next submit re-wakes it via SQ_WAKEUP. 0
159   means use the kernel default (1ms). Recommended for bursty 159   means use the kernel default (1ms). Recommended for bursty
160   workloads: 100-1000ms (avoids park/unpark thrash). 160   workloads: 100-1000ms (avoids park/unpark thrash).
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   unsigned sq_thread_idle_ms = 0; 165   unsigned sq_thread_idle_ms = 0;
166   166  
167   /** Pin the SQ-poll kernel thread to this CPU. 167   /** Pin the SQ-poll kernel thread to this CPU.
168   168  
169   -1 means do not pin (kernel scheduler picks). Pinning off 169   -1 means do not pin (kernel scheduler picks). Pinning off
170   the dispatch core is recommended on latency-sensitive 170   the dispatch core is recommended on latency-sensitive
171   deployments to avoid cache contention. 171   deployments to avoid cache contention.
172   172  
173   Ignored unless `enable_sqpoll` is true. Ignored on 173   Ignored unless `enable_sqpoll` is true. Ignored on
174   non-io_uring backends. 174   non-io_uring backends.
175   */ 175   */
176   int sq_thread_cpu = -1; 176   int sq_thread_cpu = -1;
177   }; 177   };
178   178  
179   namespace detail { 179   namespace detail {
180   class timer_service; 180   class timer_service;
181   181  
182   /** Return the hint used for performance tuning: the lockless tiers are 182   /** Return the hint used for performance tuning: the lockless tiers are
183   single-threaded, so their effective hint is 1 whatever the caller passed. 183   single-threaded, so their effective hint is 1 whatever the caller passed.
184   */ 184   */
185   inline unsigned 185   inline unsigned
HITCBC 186   36 effective_concurrency_hint( 186   36 effective_concurrency_hint(
187   io_context_options const& opts, unsigned hint) noexcept 187   io_context_options const& opts, unsigned hint) noexcept
188   { 188   {
HITCBC 189   36 return opts.locking == locking_mode::safe ? hint : 1u; 189   36 return opts.locking == locking_mode::safe ? hint : 1u;
190   } 190   }
191   } // namespace detail 191   } // namespace detail
192   192  
193   /** An I/O context for running asynchronous operations. 193   /** An I/O context for running asynchronous operations.
194   194  
195   The io_context provides an execution environment for async 195   The io_context provides an execution environment for async
196   operations. It maintains a queue of pending work items and 196   operations. It maintains a queue of pending work items and
197   processes them when `run()` is called. 197   processes them when `run()` is called.
198   198  
199   The default and unsigned constructors select the platform's 199   The default and unsigned constructors select the platform's
200   native backend: 200   native backend:
201   - Windows: IOCP 201   - Windows: IOCP
202   - Linux: epoll 202   - Linux: epoll
203   - BSD/macOS: kqueue 203   - BSD/macOS: kqueue
204   - Other POSIX: select 204   - Other POSIX: select
205   205  
206   The template constructor accepts a backend tag value to 206   The template constructor accepts a backend tag value to
207   choose a specific backend at compile time: 207   choose a specific backend at compile time:
208   208  
209   @par Example 209   @par Example
210   @code 210   @code
211   io_context ioc; // platform default 211   io_context ioc; // platform default
212   io_context ioc2(corosio::epoll); // explicit backend 212   io_context ioc2(corosio::epoll); // explicit backend
213   @endcode 213   @endcode
214   214  
215   @par Preconditions 215   @par Preconditions
216   The context must outlive every operation posted or dispatched 216   The context must outlive every operation posted or dispatched
217   through its executor, and no thread may be executing a run 217   through its executor, and no thread may be executing a run
218   variant when the context is destroyed. Posting to the context 218   variant when the context is destroyed. Posting to the context
219   concurrently with, or after, its destruction is undefined 219   concurrently with, or after, its destruction is undefined
220   behavior. The safe teardown pattern is to stop submitting new 220   behavior. The safe teardown pattern is to stop submitting new
221   work, let every `run()` call return (each returns once no 221   work, let every `run()` call return (each returns once no
222   outstanding work remains), and join the threads that ran the 222   outstanding work remains), and join the threads that ran the
223   loop before destroying the context. Work launched with 223   loop before destroying the context. Work launched with
224   `capy::run` / `capy::run_async` is work-tracked, so a normal 224   `capy::run` / `capy::run_async` is work-tracked, so a normal
225   `run()` completion already waits for it. 225   `run()` completion already waits for it.
226   226  
227   @par Thread Safety 227   @par Thread Safety
228   Distinct objects: Safe.@n 228   Distinct objects: Safe.@n
229   Shared objects: Safe, unless the context was constructed with a 229   Shared objects: Safe, unless the context was constructed with a
230   lockless @ref io_context_options::locking tier (`unsafe_io` or 230   lockless @ref io_context_options::locking tier (`unsafe_io` or
231   `unsafe`), in which case a single thread must drive it. 231   `unsafe`), in which case a single thread must drive it.
232   232  
233   @see epoll_t, select_t, kqueue_t, iocp_t 233   @see epoll_t, select_t, kqueue_t, iocp_t
234   */ 234   */
235   class BOOST_COROSIO_DECL io_context : public capy::execution_context 235   class BOOST_COROSIO_DECL io_context : public capy::execution_context
236   { 236   {
237   /// Pre-create services that depend on options (before construct). 237   /// Pre-create services that depend on options (before construct).
238   void apply_options_pre_(io_context_options const& opts); 238   void apply_options_pre_(io_context_options const& opts);
239   239  
240   /// Apply runtime tuning to the scheduler (after construct). 240   /// Apply runtime tuning to the scheduler (after construct).
241   void apply_options_post_( 241   void apply_options_post_(
242   io_context_options const& opts, 242   io_context_options const& opts,
243   unsigned concurrency_hint); 243   unsigned concurrency_hint);
244   244  
245   /** Apply only the decomposed threading configuration (locking tiers). 245   /** Apply only the decomposed threading configuration (locking tiers).
246   Used by the plain constructors, which — unlike the options 246   Used by the plain constructors, which — unlike the options
247   constructors — deliberately leave the reactor budget at its defaults 247   constructors — deliberately leave the reactor budget at its defaults
248   rather than engaging the multi-thread post-everything heuristic. */ 248   rather than engaging the multi-thread post-everything heuristic. */
249   void apply_threading_(io_context_options const& opts); 249   void apply_threading_(io_context_options const& opts);
250   250  
251   protected: 251   protected:
252   detail::scheduler* sched_; 252   detail::scheduler* sched_;
253   253  
254   public: 254   public:
255   /** The executor type for this context. */ 255   /** The executor type for this context. */
256   class executor_type; 256   class executor_type;
257   257  
258   /** Construct with default concurrency and platform backend. 258   /** Construct with default concurrency and platform backend.
259   259  
260   Uses `std::thread::hardware_concurrency()` (floored to 1, in 260   Uses `std::thread::hardware_concurrency()` (floored to 1, in
261   case it reports 0) as the concurrency hint, and the default 261   case it reports 0) as the concurrency hint, and the default
262   @ref locking_mode::safe tier. Select a lockless tier via 262   @ref locking_mode::safe tier. Select a lockless tier via
263   @ref io_context_options::locking. 263   @ref io_context_options::locking.
264   */ 264   */
265   io_context(); 265   io_context();
266   266  
267   /** Construct with a concurrency hint and platform backend. 267   /** Construct with a concurrency hint and platform backend.
268   268  
269   @param concurrency_hint Hint for the number of threads 269   @param concurrency_hint Hint for the number of threads
270   that will call `run()`. 270   that will call `run()`.
271   */ 271   */
272   explicit io_context(unsigned concurrency_hint); 272   explicit io_context(unsigned concurrency_hint);
273   273  
274   /** Construct with runtime tuning options and platform backend. 274   /** Construct with runtime tuning options and platform backend.
275   275  
276   @param opts Runtime options controlling scheduler and 276   @param opts Runtime options controlling scheduler and
277   service behavior. 277   service behavior.
278   @param concurrency_hint Hint for the number of threads 278   @param concurrency_hint Hint for the number of threads
279   that will call `run()`. 279   that will call `run()`.
  280 +
  281 + @throws std::invalid_argument If `opts.thread_pool_size` is
  282 + less than 1 (POSIX).
280   */ 283   */
281   explicit io_context( 284   explicit io_context(
282   io_context_options const& opts, 285   io_context_options const& opts,
283   unsigned concurrency_hint = std::thread::hardware_concurrency()); 286   unsigned concurrency_hint = std::thread::hardware_concurrency());
284   287  
285   /** Construct with an explicit backend tag. 288   /** Construct with an explicit backend tag.
286   289  
287   @param backend The backend tag value selecting the I/O 290   @param backend The backend tag value selecting the I/O
288   multiplexer (e.g. `corosio::epoll`). 291   multiplexer (e.g. `corosio::epoll`).
289   @param concurrency_hint Hint for the number of threads 292   @param concurrency_hint Hint for the number of threads
290   that will call `run()`. 293   that will call `run()`.
291   */ 294   */
292   template<class Backend> 295   template<class Backend>
293   requires requires { Backend::construct; } 296   requires requires { Backend::construct; }
HITCBC 294   1316 explicit io_context( 297   1386 explicit io_context(
295 - Backend backend, 298 + [[maybe_unused]] Backend backend,
296   unsigned concurrency_hint = std::thread::hardware_concurrency()) 299   unsigned concurrency_hint = std::thread::hardware_concurrency())
297   : capy::execution_context(this) 300   : capy::execution_context(this)
HITCBC 298   1316 , sched_(nullptr) 301   1386 , sched_(nullptr)
299 - (void)backend;  
300   { 302   {
HITCBC 301   1316 sched_ = &Backend::construct(*this, concurrency_hint); 303   1386 sched_ = &Backend::construct(*this, concurrency_hint);
302   // Apply threading config only (locking tier). Unlike the options 304   // Apply threading config only (locking tier). Unlike the options
303   // ctor, the plain path leaves the reactor budget at its defaults. 305   // ctor, the plain path leaves the reactor budget at its defaults.
HITCBC 304   1316 apply_threading_(io_context_options{}); 306   1386 apply_threading_(io_context_options{});
HITCBC 305   1316 } 307   1386 }
306   308  
307   /** Construct with an explicit backend tag and runtime options. 309   /** Construct with an explicit backend tag and runtime options.
308   310  
309   @param backend The backend tag value selecting the I/O 311   @param backend The backend tag value selecting the I/O
310   multiplexer (e.g. `corosio::epoll`). 312   multiplexer (e.g. `corosio::epoll`).
311   @param opts Runtime options controlling scheduler and 313   @param opts Runtime options controlling scheduler and
312   service behavior. 314   service behavior.
313   @param concurrency_hint Hint for the number of threads 315   @param concurrency_hint Hint for the number of threads
314   that will call `run()`. 316   that will call `run()`.
  317 +
  318 + @throws std::invalid_argument If `opts.thread_pool_size` is
  319 + less than 1 (POSIX).
315   */ 320   */
316   template<class Backend> 321   template<class Backend>
317   requires requires { Backend::construct; } 322   requires requires { Backend::construct; }
HITCBC 318   19 explicit io_context( 323   19 explicit io_context(
319 - Backend backend, 324 + [[maybe_unused]] Backend backend,
320   io_context_options const& opts, 325   io_context_options const& opts,
321   unsigned concurrency_hint = std::thread::hardware_concurrency()) 326   unsigned concurrency_hint = std::thread::hardware_concurrency())
322   : capy::execution_context(this) 327   : capy::execution_context(this)
HITCBC 323   19 , sched_(nullptr) 328   19 , sched_(nullptr)
324 - (void)backend;  
325   { 329   {
HITCBC 326   19 apply_options_pre_(opts); 330   19 apply_options_pre_(opts);
327   // Effective hint (1 for lockless tiers); see effective_concurrency_hint. 331   // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
328   unsigned const eff = 332   unsigned const eff =
HITCBC 329   19 detail::effective_concurrency_hint(opts, concurrency_hint); 333   19 detail::effective_concurrency_hint(opts, concurrency_hint);
HITCBC 330   19 sched_ = &Backend::construct(*this, eff); 334   19 sched_ = &Backend::construct(*this, eff);
HITCBC 331   19 apply_options_post_(opts, eff); 335   19 apply_options_post_(opts, eff);
HITCBC 332   19 } 336   19 }
333   337  
334   ~io_context(); 338   ~io_context();
335   339  
336   io_context(io_context const&) = delete; 340   io_context(io_context const&) = delete;
337   io_context& operator=(io_context const&) = delete; 341   io_context& operator=(io_context const&) = delete;
338   342  
339   /** Return an executor for this context. 343   /** Return an executor for this context.
340   344  
341   The returned executor can be used to dispatch coroutines 345   The returned executor can be used to dispatch coroutines
342   and post work items to this context. 346   and post work items to this context.
343   347  
344   @return An executor associated with this context. 348   @return An executor associated with this context.
345   */ 349   */
346   executor_type get_executor() const noexcept; 350   executor_type get_executor() const noexcept;
347   351  
348   /** Signal the context to stop processing. 352   /** Signal the context to stop processing.
349   353  
350   This causes `run()` to return as soon as possible. Any pending 354   This causes `run()` to return as soon as possible. Any pending
351   work items remain queued. 355   work items remain queued.
352   */ 356   */
HITCBC 353   7 void stop() 357   7 void stop()
354   { 358   {
HITCBC 355   7 sched_->stop(); 359   7 sched_->stop();
HITCBC 356   7 } 360   7 }
357   361  
358   /** Return whether the context has been stopped. 362   /** Return whether the context has been stopped.
359   363  
360   @return `true` if `stop()` has been called and `restart()` 364   @return `true` if `stop()` has been called and `restart()`
361   has not been called since. 365   has not been called since.
362   */ 366   */
HITCBC 363   74 bool stopped() const noexcept 367   74 bool stopped() const noexcept
364   { 368   {
HITCBC 365   74 return sched_->stopped(); 369   74 return sched_->stopped();
366   } 370   }
367   371  
368   /** Restart the context after being stopped. 372   /** Restart the context after being stopped.
369   373  
370   This function must be called before `run()` can be called 374   This function must be called before `run()` can be called
371   again after `stop()` has been called. 375   again after `stop()` has been called.
372   */ 376   */
HITCBC 373   311 void restart() 377   311 void restart()
374   { 378   {
HITCBC 375   311 sched_->restart(); 379   311 sched_->restart();
HITCBC 376   311 } 380   311 }
377   381  
378   /** Process all pending work items. 382   /** Process all pending work items.
379   383  
380   This function blocks until all pending work items have been 384   This function blocks until all pending work items have been
381   executed or `stop()` is called. The context is stopped 385   executed or `stop()` is called. The context is stopped
382   when there is no more outstanding work. 386   when there is no more outstanding work.
383   387  
384   @note The context must be restarted with `restart()` before 388   @note The context must be restarted with `restart()` before
385   calling this function again after it returns. 389   calling this function again after it returns.
386   390  
387   @return The number of handlers executed. 391   @return The number of handlers executed.
388   */ 392   */
HITCBC 389   1258 std::size_t run() 393   1316 std::size_t run()
390   { 394   {
HITCBC 391   1258 return sched_->run(); 395   1316 return sched_->run();
392   } 396   }
393   397  
394   /** Process at most one pending work item. 398   /** Process at most one pending work item.
395   399  
396   This function blocks until one work item has been executed 400   This function blocks until one work item has been executed
397   or `stop()` is called. The context is stopped when there 401   or `stop()` is called. The context is stopped when there
398   is no more outstanding work. 402   is no more outstanding work.
399   403  
400   @note The context must be restarted with `restart()` before 404   @note The context must be restarted with `restart()` before
401   calling this function again after it returns. 405   calling this function again after it returns.
402   406  
403   @return The number of handlers executed (0 or 1). 407   @return The number of handlers executed (0 or 1).
404   */ 408   */
HITCBC 405   29 std::size_t run_one() 409   29 std::size_t run_one()
406   { 410   {
HITCBC 407   29 return sched_->run_one(); 411   29 return sched_->run_one();
408   } 412   }
409   413  
410   /** Process work items for the specified duration. 414   /** Process work items for the specified duration.
411   415  
412   This function blocks until work items have been executed for 416   This function blocks until work items have been executed for
413   the specified duration, or `stop()` is called. The context 417   the specified duration, or `stop()` is called. The context
414   is stopped when there is no more outstanding work. 418   is stopped when there is no more outstanding work.
415   419  
416   @note The context must be restarted with `restart()` before 420   @note The context must be restarted with `restart()` before
417   calling this function again after it returns. 421   calling this function again after it returns.
418   422  
419   @param rel_time The duration for which to process work. 423   @param rel_time The duration for which to process work.
420   424  
421   @return The number of handlers executed. 425   @return The number of handlers executed.
422   */ 426   */
423   template<class Rep, class Period> 427   template<class Rep, class Period>
HITCBC 424   11 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time) 428   11 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
425   { 429   {
HITCBC 426   11 return run_until(std::chrono::steady_clock::now() + rel_time); 430   11 return run_until(std::chrono::steady_clock::now() + rel_time);
427   } 431   }
428   432  
429   /** Process work items until the specified time. 433   /** Process work items until the specified time.
430   434  
431   This function blocks until the specified time is reached 435   This function blocks until the specified time is reached
432   or `stop()` is called. The context is stopped when there 436   or `stop()` is called. The context is stopped when there
433   is no more outstanding work. 437   is no more outstanding work.
434   438  
435   @note The context must be restarted with `restart()` before 439   @note The context must be restarted with `restart()` before
436   calling this function again after it returns. 440   calling this function again after it returns.
437   441  
438   @param abs_time The time point until which to process work. 442   @param abs_time The time point until which to process work.
439   443  
440   @return The number of handlers executed. 444   @return The number of handlers executed.
441   */ 445   */
442   template<class Clock, class Duration> 446   template<class Clock, class Duration>
443   std::size_t 447   std::size_t
HITCBC 444   12 run_until(std::chrono::time_point<Clock, Duration> const& abs_time) 448   12 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
445   { 449   {
HITCBC 446   12 std::size_t n = 0; 450   12 std::size_t n = 0;
HITCBC 447   30 while (run_one_until(abs_time)) 451   30 while (run_one_until(abs_time))
HITCBC 448   18 if (n != (std::numeric_limits<std::size_t>::max)()) 452   18 if (n != (std::numeric_limits<std::size_t>::max)())
HITCBC 449   18 ++n; 453   18 ++n;
HITCBC 450   12 return n; 454   12 return n;
451   } 455   }
452   456  
453   /** Process at most one work item for the specified duration. 457   /** Process at most one work item for the specified duration.
454   458  
455   This function blocks until one work item has been executed, 459   This function blocks until one work item has been executed,
456   the specified duration has elapsed, or `stop()` is called. 460   the specified duration has elapsed, or `stop()` is called.
457   The context is stopped when there is no more outstanding work. 461   The context is stopped when there is no more outstanding work.
458   462  
459   @note The context must be restarted with `restart()` before 463   @note The context must be restarted with `restart()` before
460   calling this function again after it returns. 464   calling this function again after it returns.
461   465  
462   @param rel_time The duration for which the call may block. 466   @param rel_time The duration for which the call may block.
463   467  
464   @return The number of handlers executed (0 or 1). 468   @return The number of handlers executed (0 or 1).
465   */ 469   */
466   template<class Rep, class Period> 470   template<class Rep, class Period>
HITCBC 467   6 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time) 471   6 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
468   { 472   {
HITCBC 469   6 return run_one_until(std::chrono::steady_clock::now() + rel_time); 473   6 return run_one_until(std::chrono::steady_clock::now() + rel_time);
470   } 474   }
471   475  
472   /** Process at most one work item until the specified time. 476   /** Process at most one work item until the specified time.
473   477  
474   This function blocks until one work item has been executed, 478   This function blocks until one work item has been executed,
475   the specified time is reached, or `stop()` is called. 479   the specified time is reached, or `stop()` is called.
476   The context is stopped when there is no more outstanding work. 480   The context is stopped when there is no more outstanding work.
477   481  
478   @note The context must be restarted with `restart()` before 482   @note The context must be restarted with `restart()` before
479   calling this function again after it returns. 483   calling this function again after it returns.
480   484  
481   @param abs_time The time point until which the call may block. 485   @param abs_time The time point until which the call may block.
482   486  
483   @return The number of handlers executed (0 or 1). 487   @return The number of handlers executed (0 or 1).
484   */ 488   */
485   template<class Clock, class Duration> 489   template<class Clock, class Duration>
486   std::size_t 490   std::size_t
HITCBC 487   44 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time) 491   44 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
488   { 492   {
HITCBC 489   44 typename Clock::time_point now = Clock::now(); 493   44 typename Clock::time_point now = Clock::now();
HITCBC 490   8 for (;;) 494   8 for (;;)
491   { 495   {
HITCBC 492   52 auto rel_time = abs_time - now; 496   52 auto rel_time = abs_time - now;
493   using rel_type = decltype(rel_time); 497   using rel_type = decltype(rel_time);
HITCBC 494   52 if (rel_time < rel_type::zero()) 498   52 if (rel_time < rel_type::zero())
HITCBC 495   5 rel_time = rel_type::zero(); 499   5 rel_time = rel_type::zero();
HITCBC 496   47 else if (rel_time > std::chrono::seconds(1)) 500   47 else if (rel_time > std::chrono::seconds(1))
HITCBC 497   22 rel_time = std::chrono::seconds(1); 501   22 rel_time = std::chrono::seconds(1);
498   502  
HITCBC 499   52 std::size_t s = sched_->wait_one( 503   52 std::size_t s = sched_->wait_one(
500   static_cast<long>( 504   static_cast<long>(
HITCBC 501   52 std::chrono::duration_cast<std::chrono::microseconds>( 505   52 std::chrono::duration_cast<std::chrono::microseconds>(
502   rel_time) 506   rel_time)
HITCBC 503   52 .count())); 507   52 .count()));
504   508  
HITCBC 505   52 if (s || stopped()) 509   52 if (s || stopped())
HITCBC 506   44 return s; 510   44 return s;
507   511  
HITCBC 508   12 now = Clock::now(); 512   12 now = Clock::now();
HITCBC 509   12 if (now >= abs_time) 513   12 if (now >= abs_time)
HITCBC 510   4 return 0; 514   4 return 0;
511   } 515   }
512   } 516   }
513   517  
514   /** Process all ready work items without blocking. 518   /** Process all ready work items without blocking.
515   519  
516   This function executes all work items that are ready to run 520   This function executes all work items that are ready to run
517   without blocking for more work. The context is stopped 521   without blocking for more work. The context is stopped
518   when there is no more outstanding work. 522   when there is no more outstanding work.
519   523  
520   @note The context must be restarted with `restart()` before 524   @note The context must be restarted with `restart()` before
521   calling this function again after it returns. 525   calling this function again after it returns.
522   526  
523   @return The number of handlers executed. 527   @return The number of handlers executed.
524   */ 528   */
HITCBC 525   31 std::size_t poll() 529   31 std::size_t poll()
526   { 530   {
HITCBC 527   31 return sched_->poll(); 531   31 return sched_->poll();
528   } 532   }
529   533  
530   /** Process at most one ready work item without blocking. 534   /** Process at most one ready work item without blocking.
531   535  
532   This function executes at most one work item that is ready 536   This function executes at most one work item that is ready
533   to run without blocking for more work. The context is 537   to run without blocking for more work. The context is
534   stopped when there is no more outstanding work. 538   stopped when there is no more outstanding work.
535   539  
536   @note The context must be restarted with `restart()` before 540   @note The context must be restarted with `restart()` before
537   calling this function again after it returns. 541   calling this function again after it returns.
538   542  
539   @return The number of handlers executed (0 or 1). 543   @return The number of handlers executed (0 or 1).
540   */ 544   */
HITCBC 541   9 std::size_t poll_one() 545   9 std::size_t poll_one()
542   { 546   {
HITCBC 543   9 return sched_->poll_one(); 547   9 return sched_->poll_one();
544   } 548   }
545   }; 549   };
546   550  
547   /** An executor for dispatching work to an I/O context. 551   /** An executor for dispatching work to an I/O context.
548   552  
549   The executor provides the interface for posting work items and 553   The executor provides the interface for posting work items and
550   dispatching coroutines to the associated context. It satisfies 554   dispatching coroutines to the associated context. It satisfies
551   the `capy::Executor` concept. 555   the `capy::Executor` concept.
552   556  
553   Executors are lightweight handles that can be copied and compared 557   Executors are lightweight handles that can be copied and compared
554   for equality. Two executors compare equal if they refer to the 558   for equality. Two executors compare equal if they refer to the
555   same context. 559   same context.
556   560  
557   @par Thread Safety 561   @par Thread Safety
558   Distinct objects: Safe.@n 562   Distinct objects: Safe.@n
559   Shared objects: Safe. 563   Shared objects: Safe.
560   */ 564   */
561   class io_context::executor_type 565   class io_context::executor_type
562   { 566   {
563   io_context* ctx_ = nullptr; 567   io_context* ctx_ = nullptr;
564   568  
565   public: 569   public:
566   /** Default constructor. 570   /** Default constructor.
567   571  
568   Constructs an executor not associated with any context. 572   Constructs an executor not associated with any context.
569   */ 573   */
HITCBC 570   2053 executor_type() = default; 574   2053 executor_type() = default;
571   575  
572   /** Construct an executor from a context. 576   /** Construct an executor from a context.
573   577  
574   @param ctx The context to associate with this executor. 578   @param ctx The context to associate with this executor.
575   */ 579   */
HITCBC 576   3604 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {} 580   3662 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
577   581  
578   /** Return a reference to the associated execution context. 582   /** Return a reference to the associated execution context.
579   583  
580   @return Reference to the context. 584   @return Reference to the context.
581   */ 585   */
HITCBC 582   18189 io_context& context() const noexcept 586   19429 io_context& context() const noexcept
583   { 587   {
HITCBC 584   18189 return *ctx_; 588   19429 return *ctx_;
585   } 589   }
586   590  
587   /** Check if the current thread is running this executor's context. 591   /** Check if the current thread is running this executor's context.
588   592  
589   @return `true` if `run()` is being called on this thread. 593   @return `true` if `run()` is being called on this thread.
590   */ 594   */
HITCBC 591   7662 bool running_in_this_thread() const noexcept 595   7751 bool running_in_this_thread() const noexcept
592   { 596   {
HITCBC 593   7662 return ctx_->sched_->running_in_this_thread(); 597   7751 return ctx_->sched_->running_in_this_thread();
594   } 598   }
595   599  
596   /** Informs the executor that work is beginning. 600   /** Informs the executor that work is beginning.
597   601  
598   Must be paired with `on_work_finished()`. 602   Must be paired with `on_work_finished()`.
599   */ 603   */
HITCBC 600   7951 void on_work_started() const noexcept 604   8050 void on_work_started() const noexcept
601   { 605   {
HITCBC 602   7951 ctx_->sched_->work_started(); 606   8050 ctx_->sched_->work_started();
HITCBC 603   7951 } 607   8050 }
604   608  
605   /** Informs the executor that work has completed. 609   /** Informs the executor that work has completed.
606   610  
607   @par Preconditions 611   @par Preconditions
608   A preceding call to `on_work_started()` on an equal executor. 612   A preceding call to `on_work_started()` on an equal executor.
609   */ 613   */
HITCBC 610   7899 void on_work_finished() const noexcept 614   7998 void on_work_finished() const noexcept
611   { 615   {
HITCBC 612   7899 ctx_->sched_->work_finished(); 616   7998 ctx_->sched_->work_finished();
HITCBC 613   7899 } 617   7998 }
614   618  
615   /** Dispatch a continuation. 619   /** Dispatch a continuation.
616   620  
617   Returns a handle for symmetric transfer. If called from 621   Returns a handle for symmetric transfer. If called from
618   within `run()`, returns `c.h`. Otherwise posts `c` for 622   within `run()`, returns `c.h`. Otherwise posts `c` for
619   later execution and returns `std::noop_coroutine()`. 623   later execution and returns `std::noop_coroutine()`.
620   624  
621   @param c The continuation to dispatch. 625   @param c The continuation to dispatch.
622   626  
623   @return A handle for symmetric transfer or `std::noop_coroutine()`. 627   @return A handle for symmetric transfer or `std::noop_coroutine()`.
624   628  
625   @par Preconditions 629   @par Preconditions
626   The associated context must outlive this call. Dispatching 630   The associated context must outlive this call. Dispatching
627   concurrently with, or after, the context's destruction is 631   concurrently with, or after, the context's destruction is
628   undefined behavior. 632   undefined behavior.
629   */ 633   */
HITCBC 630   7657 std::coroutine_handle<> dispatch(capy::continuation& c) const 634   7746 std::coroutine_handle<> dispatch(capy::continuation& c) const
631   { 635   {
HITCBC 632   7657 if (running_in_this_thread()) 636   7746 if (running_in_this_thread())
HITCBC 633   677 return c.h; 637   704 return c.h;
HITCBC 634   6980 post(c); 638   7042 post(c);
HITCBC 635   6980 return std::noop_coroutine(); 639   7042 return std::noop_coroutine();
636   } 640   }
637   641  
638   /** Post a continuation for deferred execution. 642   /** Post a continuation for deferred execution.
639   643  
640   Enqueues `c` directly on the scheduler's ready queue. 644   Enqueues `c` directly on the scheduler's ready queue.
641   No heap allocation occurs. 645   No heap allocation occurs.
642   646  
643   @par Preconditions 647   @par Preconditions
644   The associated context must outlive this call. Posting 648   The associated context must outlive this call. Posting
645   concurrently with, or after, the context's destruction is 649   concurrently with, or after, the context's destruction is
646   undefined behavior. 650   undefined behavior.
647   */ 651   */
HITCBC 648   15832 void post(capy::continuation& c) const 652   17045 void post(capy::continuation& c) const
649   { 653   {
HITCBC 650   15832 ctx_->sched_->post(c); 654   17045 ctx_->sched_->post(c);
HITCBC 651   15832 } 655   17045 }
652   656  
653   /** Post a bare coroutine handle for deferred execution. 657   /** Post a bare coroutine handle for deferred execution.
654   658  
655   Heap-allocates a scheduler_op to wrap the handle. A caller 659   Heap-allocates a scheduler_op to wrap the handle. A caller
656   that already owns a `scheduler_op` can post it directly via 660   that already owns a `scheduler_op` can post it directly via
657   the `post(scheduler_op*)` overload to avoid the allocation. 661   the `post(scheduler_op*)` overload to avoid the allocation.
658   662  
659   @param h The coroutine handle to post. 663   @param h The coroutine handle to post.
660   664  
661   @par Preconditions 665   @par Preconditions
662   The associated context must outlive this call. Posting 666   The associated context must outlive this call. Posting
663   concurrently with, or after, the context's destruction is 667   concurrently with, or after, the context's destruction is
664   undefined behavior. 668   undefined behavior.
665   */ 669   */
HITCBC 666   3686 void post(std::coroutine_handle<> h) const 670   3686 void post(std::coroutine_handle<> h) const
667   { 671   {
HITCBC 668   3686 ctx_->sched_->post(h); 672   3686 ctx_->sched_->post(h);
HITCBC 669   3686 } 673   3686 }
670   674  
671   /** Compare two executors for equality. 675   /** Compare two executors for equality.
672   676  
673   @return `true` if both executors refer to the same context. 677   @return `true` if both executors refer to the same context.
674   */ 678   */
HITCBC 675   2 bool operator==(executor_type const& other) const noexcept 679   2 bool operator==(executor_type const& other) const noexcept
676   { 680   {
HITCBC 677   2 return ctx_ == other.ctx_; 681   2 return ctx_ == other.ctx_;
678   } 682   }
679   683  
680   /** Compare two executors for inequality. 684   /** Compare two executors for inequality.
681   685  
682   @return `true` if the executors refer to different contexts. 686   @return `true` if the executors refer to different contexts.
683   */ 687   */
684   bool operator!=(executor_type const& other) const noexcept 688   bool operator!=(executor_type const& other) const noexcept
685   { 689   {
686   return ctx_ != other.ctx_; 690   return ctx_ != other.ctx_;
687   } 691   }
688   }; 692   };
689   693  
690   inline io_context::executor_type 694   inline io_context::executor_type
HITCBC 691   3604 io_context::get_executor() const noexcept 695   3662 io_context::get_executor() const noexcept
692   { 696   {
HITCBC 693   3604 return executor_type(const_cast<io_context&>(*this)); 697   3662 return executor_type(const_cast<io_context&>(*this));
694   } 698   }
695   699  
696   } // namespace boost::corosio 700   } // namespace boost::corosio
697   701  
698   #endif // BOOST_COROSIO_IO_CONTEXT_HPP 702   #endif // BOOST_COROSIO_IO_CONTEXT_HPP