100.00% Lines (98/98) 100.00% Functions (27/27)
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   #include <boost/capy/ex/frame_allocator.hpp> 20   #include <boost/capy/ex/frame_allocator.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   /** Selects which internal locks the scheduler and reactor elide, 30   /** Selects which internal locks the scheduler and reactor elide,
31   trading thread-safety guarantees for reduced synchronization 31   trading thread-safety guarantees for reduced synchronization
32   overhead. 32   overhead.
33   33  
34   This is the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE` 34   This is the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE`
35   concurrency hint constants. The tier is chosen explicitly, not derived 35   concurrency hint constants. The tier is chosen explicitly, not derived
36   from the `concurrency_hint`. (The reverse does apply: a lockless tier 36   from the `concurrency_hint`. (The reverse does apply: a lockless tier
37   reduces the effective hint used for performance tuning to 1.) 37   reduces the effective hint used for performance tuning to 1.)
38   38  
39   @see @ref io_context_options::locking 39   @see @ref io_context_options::locking
40   */ 40   */
41   enum class locking_mode 41   enum class locking_mode
42   { 42   {
43   /** Full thread safety (default). All locks enabled; equivalent to 43   /** Full thread safety (default). All locks enabled; equivalent to
44   Boost.Asio's `SAFE`/`DEFAULT`. Any thread may use the context. */ 44   Boost.Asio's `SAFE`/`DEFAULT`. Any thread may use the context. */
45   safe, 45   safe,
46   46  
47   /** Disable only the per-descriptor I/O locks; keep scheduler locking. 47   /** Disable only the per-descriptor I/O locks; keep scheduler locking.
48   Equivalent to Boost.Asio's `UNSAFE_IO`. A single thread must run 48   Equivalent to Boost.Asio's `UNSAFE_IO`. A single thread must run
49   and drive the context. Resolver and POSIX file services remain 49   and drive the context. Resolver and POSIX file services remain
50   available, because they rely on scheduler locking, which stays 50   available, because they rely on scheduler locking, which stays
51   on. */ 51   on. */
52   unsafe_io, 52   unsafe_io,
53   53  
54   /** Disable all locking (fully lockless). Equivalent to Boost.Asio's 54   /** Disable all locking (fully lockless). Equivalent to Boost.Asio's
55   `UNSAFE`. 55   `UNSAFE`.
56   56  
57   @par Restrictions 57   @par Restrictions
58   - Only one thread may call `run()` (or any run variant). 58   - Only one thread may call `run()` (or any run variant).
59   - Posting work from another thread is undefined behavior. 59   - Posting work from another thread is undefined behavior.
60   - DNS resolution returns `operation_not_supported`, except on 60   - DNS resolution returns `operation_not_supported`, except on
61   IOCP, whose resolver does not use the thread pool. 61   IOCP, whose resolver does not use the thread pool.
62   - POSIX file I/O returns `operation_not_supported`. 62   - POSIX file I/O returns `operation_not_supported`.
63   - `win_object_handle::assign()` returns `operation_not_supported`. 63   - `win_object_handle::assign()` returns `operation_not_supported`.
64   - Signal sets should not be shared across contexts. */ 64   - Signal sets should not be shared across contexts. */
65   unsafe 65   unsafe
66   }; 66   };
67   67  
68   /** Configures scheduler and reactor tuning for an @ref io_context. 68   /** Configures scheduler and reactor tuning for an @ref io_context.
69   69  
70   All fields have defaults that match the library's built-in 70   All fields have defaults that match the library's built-in
71   values, so constructing a default `io_context_options` produces 71   values, so constructing a default `io_context_options` produces
72   identical behavior to an unconfigured context. 72   identical behavior to an unconfigured context.
73   73  
74 - Options that apply only to a specific backend family are 74 + Options that apply only to a specific backend family are ignored
75 - silently ignored when the active backend does not support them. 75 + when the active backend does not use them. The exceptions are
  76 + `max_events_per_poll` and `inline_budget_max`, which are
  77 + range-checked on every platform with a reactor backend.
76   78  
77   @par Example 79   @par Example
78   @par !example configure 80   @par !example configure
79   81  
80   @see @ref io_context, @ref native_io_context 82   @see @ref io_context, @ref native_io_context
81   */ 83   */
82   struct io_context_options 84   struct io_context_options
83   { 85   {
84   /** Maximum events fetched per reactor poll call. 86   /** Maximum events fetched per reactor poll call.
85   87  
86   Controls the buffer size passed to `epoll_wait()` or 88   Controls the buffer size passed to `epoll_wait()` or
87   `kevent()`. Larger values reduce syscall frequency under 89   `kevent()`. Larger values reduce syscall frequency under
88   high load. Smaller values improve fairness between 90   high load. Smaller values improve fairness between
89 - connections. Ignored on IOCP and select backends. 91 + connections. Must be in [ 1, `INT_MAX` ]. Where epoll is
  92 + available, it must also not exceed
  93 + `INT_MAX / sizeof(epoll_event)`, the most events one epoll wait
  94 + accepts. Validated on platforms with a reactor backend,
  95 + whichever backend is active, but only the epoll and kqueue
  96 + backends use it.
90   */ 97   */
91   unsigned max_events_per_poll = 128; 98   unsigned max_events_per_poll = 128;
92   99  
93   /** Starting inline completion budget per handler chain. 100   /** Starting inline completion budget per handler chain.
94   101  
95   After a posted handler executes, the reactor grants this 102   After a posted handler executes, the reactor grants this
96   many speculative inline completions before forcing a 103   many speculative inline completions before forcing a
97   re-queue. Applies to reactor backends only. 104   re-queue. Applies to reactor backends only.
98   105  
99   @note Constructing an `io_context` with `concurrency_hint > 1` 106   @note Constructing an `io_context` with `concurrency_hint > 1`
100   and all three budget fields at their defaults overrides them to 107   and all three budget fields at their defaults overrides them to
101   disable inline completion, giving post-everything mode. 108   disable inline completion, giving post-everything mode.
102   Multi-thread workloads benefit from cross-thread work-stealing. 109   Multi-thread workloads benefit from cross-thread work-stealing.
103   Setting any budget field to a non-default 110   Setting any budget field to a non-default
104   value disables the override. 111   value disables the override.
105   */ 112   */
106   unsigned inline_budget_initial = 2; 113   unsigned inline_budget_initial = 2;
107   114  
108   /** Hard ceiling on adaptive inline budget ramp-up. 115   /** Hard ceiling on adaptive inline budget ramp-up.
109   116  
110   The budget doubles each cycle it is fully consumed, up to 117   The budget doubles each cycle it is fully consumed, up to
111 - this limit. Applies to reactor backends only. 118 + this limit. Must be in [ 0, `INT_MAX` ]; validated on
  119 + platforms with a reactor backend, whichever backend is
  120 + active. Applies to reactor backends only.
112   */ 121   */
113   unsigned inline_budget_max = 16; 122   unsigned inline_budget_max = 16;
114   123  
115   /** Inline budget when no other thread assists the reactor. 124   /** Inline budget when no other thread assists the reactor.
116   125  
117   When only one thread is running the event loop, this 126   When only one thread is running the event loop, this
118   value caps the inline budget to preserve fairness. 127   value caps the inline budget to preserve fairness.
119   Applies to reactor backends only. 128   Applies to reactor backends only.
120   */ 129   */
121   unsigned unassisted_budget = 4; 130   unsigned unassisted_budget = 4;
122   131  
123   /** Thread pool size for blocking I/O (file I/O, DNS resolution). 132   /** Thread pool size for blocking I/O (file I/O, DNS resolution).
124   133  
125   Sets the number of worker threads in the shared thread pool 134   Sets the number of worker threads in the shared thread pool
126   used by POSIX file services and DNS resolution. Must be at 135   used by POSIX file services and DNS resolution. Must be at
127   least 1. Applies to POSIX backends only; ignored on IOCP 136   least 1. Applies to POSIX backends only; ignored on IOCP
128   where file I/O uses native overlapped I/O. 137   where file I/O uses native overlapped I/O.
129   */ 138   */
130   unsigned thread_pool_size = 1; 139   unsigned thread_pool_size = 1;
131   140  
132   /** Thread-safety tier. See @ref locking_mode for the tiers and their 141   /** Thread-safety tier. See @ref locking_mode for the tiers and their
133   restrictions. 142   restrictions.
134   */ 143   */
135   locking_mode locking = locking_mode::safe; 144   locking_mode locking = locking_mode::safe;
136   145  
137   /** Enable IORING_SETUP_SQPOLL on the io_uring backend. 146   /** Enable IORING_SETUP_SQPOLL on the io_uring backend.
138   147  
139   With SQPOLL, the kernel forks a thread that busy-polls the 148   With SQPOLL, the kernel forks a thread that busy-polls the
140   submission ring. Submission becomes a userspace-only memory 149   submission ring. Submission becomes a userspace-only memory
141   store, which eliminates the `io_uring_enter` syscall on the submit 150   store, which eliminates the `io_uring_enter` syscall on the submit
142   path. Most useful for sustained traffic. Idle thread parks 151   path. Most useful for sustained traffic. Idle thread parks
143   after `sq_thread_idle_ms` of no activity. 152   after `sq_thread_idle_ms` of no activity.
144   153  
145   Independent of `locking`. Default: off. 154   Independent of `locking`. Default: off.
146   155  
147   Ignored on non-io_uring backends. 156   Ignored on non-io_uring backends.
148   */ 157   */
149   bool enable_sqpoll = false; 158   bool enable_sqpoll = false;
150   159  
151   /** SQ-poll idle timeout in milliseconds. 160   /** SQ-poll idle timeout in milliseconds.
152   161  
153   After this many ms of no submissions, the kernel polling 162   After this many ms of no submissions, the kernel polling
154   thread sleeps. The next submit re-wakes it via SQ_WAKEUP. 0 163   thread sleeps. The next submit re-wakes it via SQ_WAKEUP. 0
155   means use the kernel default (1ms). Recommended for bursty 164   means use the kernel default (1ms). Recommended for bursty
156   workloads: 100-1000ms (avoids park/unpark thrash). 165   workloads: 100-1000ms (avoids park/unpark thrash).
157   166  
158   Ignored unless `enable_sqpoll` is true. Ignored on 167   Ignored unless `enable_sqpoll` is true. Ignored on
159   non-io_uring backends. 168   non-io_uring backends.
160   */ 169   */
161   unsigned sq_thread_idle_ms = 0; 170   unsigned sq_thread_idle_ms = 0;
162   171  
163   /** Pin the SQ-poll kernel thread to this CPU. 172   /** Pin the SQ-poll kernel thread to this CPU.
164   173  
165   -1 means do not pin (kernel scheduler picks). Pinning off 174   -1 means do not pin (kernel scheduler picks). Pinning off
166   the dispatch core is recommended on latency-sensitive 175   the dispatch core is recommended on latency-sensitive
167   deployments to avoid cache contention. 176   deployments to avoid cache contention.
168   177  
169   Ignored unless `enable_sqpoll` is true. Ignored on 178   Ignored unless `enable_sqpoll` is true. Ignored on
170   non-io_uring backends. 179   non-io_uring backends.
171   */ 180   */
172   int sq_thread_cpu = -1; 181   int sq_thread_cpu = -1;
173   }; 182   };
174   183  
175   namespace detail { 184   namespace detail {
176   class timer_service; 185   class timer_service;
177   186  
178   /** Return the hint used for performance tuning: the lockless tiers are 187   /** Return the hint used for performance tuning: the lockless tiers are
179   single-threaded, so their effective hint is 1 whatever the caller passed. 188   single-threaded, so their effective hint is 1 whatever the caller passed.
180   */ 189   */
181   inline unsigned 190   inline unsigned
HITCBC 182   62 effective_concurrency_hint( 191   58 effective_concurrency_hint(
183   io_context_options const& opts, unsigned hint) noexcept 192   io_context_options const& opts, unsigned hint) noexcept
184   { 193   {
HITCBC 185   62 return opts.locking == locking_mode::safe ? hint : 1u; 194   58 return opts.locking == locking_mode::safe ? hint : 1u;
186   } 195   }
187   } // namespace detail 196   } // namespace detail
188   197  
189   /** Runs asynchronous operations and owns the I/O backend that drives them. 198   /** Runs asynchronous operations and owns the I/O backend that drives them.
190   199  
191   The `io_context` provides an execution environment for async 200   The `io_context` provides an execution environment for async
192   operations. It maintains a queue of pending work items and 201   operations. It maintains a queue of pending work items and
193   processes them when `run()` is called. 202   processes them when `run()` is called.
194   203  
195   The default and unsigned constructors select the platform's 204   The default and unsigned constructors select the platform's
196   native backend: 205   native backend:
197   - Windows: IOCP 206   - Windows: IOCP
198   - Linux: epoll 207   - Linux: epoll
199   - BSD/macOS: kqueue 208   - BSD/macOS: kqueue
200   - Other POSIX: select 209   - Other POSIX: select
201   210  
202   The template constructor accepts a backend tag value to 211   The template constructor accepts a backend tag value to
203   choose a specific backend at compile time: 212   choose a specific backend at compile time:
204   213  
205   @par Example 214   @par Example
206   @par !example construct 215   @par !example construct
207   216  
208   @pre The context must outlive every operation posted or dispatched 217   @pre The context must outlive every operation posted or dispatched
209   through its executor. No thread may be executing a run variant when 218   through its executor. No thread may be executing a run variant when
210   the context is destroyed. Posting to the context 219   the context is destroyed. Posting to the context
211   concurrently with, or after, its destruction is undefined 220   concurrently with, or after, its destruction is undefined
212   behavior. For a safe teardown, first stop submitting new work. 221   behavior. For a safe teardown, first stop submitting new work.
213   Then let every `run()` call return; each returns once no 222   Then let every `run()` call return; each returns once no
214   outstanding work remains. Finally join the threads that ran the 223   outstanding work remains. Finally join the threads that ran the
215   loop. Only then destroy the context. Work started with 224   loop. Only then destroy the context. Work started with
216   `capy::run` / `capy::run_async` is work-tracked, so a normal 225   `capy::run` / `capy::run_async` is work-tracked, so a normal
217   `run()` completion already waits for it. Destroying the context 226   `run()` completion already waits for it. Destroying the context
218   abandons every coroutine still suspended in it (see 227   abandons every coroutine still suspended in it (see
219   @ref ~io_context). 228   @ref ~io_context).
220   229  
221   @par Exception Safety 230   @par Exception Safety
222   A context that constructs is usable. The infrastructure its backend 231   A context that constructs is usable. The infrastructure its backend
223   needs — the completion port, the ring, the reactor's wakeup channel 232   needs — the completion port, the ring, the reactor's wakeup channel
224   — is created during construction. A system that refuses it therefore 233   — is created during construction. A system that refuses it therefore
225   throws from the constructor rather than from the first operation. 234   throws from the constructor rather than from the first operation.
226   The failed construction leaves nothing open. 235   The failed construction leaves nothing open.
227   236  
228   An operation that completes resumes its coroutine through the 237   An operation that completes resumes its coroutine through the
229   executor the coroutine awaited it on. If that executor's `post` or 238   executor the coroutine awaited it on. If that executor's `post` or
230   `dispatch` throws, `std::terminate` is called. 239   `dispatch` throws, `std::terminate` is called.
231   240  
232   @par Thread Safety 241   @par Thread Safety
233   Distinct objects: Safe.@n 242   Distinct objects: Safe.@n
234   Shared objects: Safe, unless the context was constructed with a 243   Shared objects: Safe, unless the context was constructed with a
235   lockless @ref io_context_options::locking tier (`unsafe_io` or 244   lockless @ref io_context_options::locking tier (`unsafe_io` or
236   `unsafe`), in which case a single thread must drive it. 245   `unsafe`), in which case a single thread must drive it.
237   246  
238   @see @ref epoll_t, @ref select_t, @ref kqueue_t, @ref iocp_t 247   @see @ref epoll_t, @ref select_t, @ref kqueue_t, @ref iocp_t
239   */ 248   */
240   class BOOST_COROSIO_DECL io_context : public capy::execution_context 249   class BOOST_COROSIO_DECL io_context : public capy::execution_context
241   { 250   {
242   /// Reject invalid options before the backend is constructed. 251   /// Reject invalid options before the backend is constructed.
243   void apply_options_pre_(io_context_options const& opts); 252   void apply_options_pre_(io_context_options const& opts);
244   253  
245   /** Create the blocking-I/O thread pool, apply runtime tuning to the 254   /** Create the blocking-I/O thread pool, apply runtime tuning to the
246   scheduler and finish bringing the backend up. The tail of every 255   scheduler and finish bringing the backend up. The tail of every
247   options constructor. The backend infrastructure whose setup reads 256   options constructor. The backend infrastructure whose setup reads
248   these options is created here, so a failure to create it throws 257   these options is created here, so a failure to create it throws
249   from the constructor. */ 258   from the constructor. */
250   void apply_options_post_( 259   void apply_options_post_(
251   io_context_options const& opts, unsigned concurrency_hint); 260   io_context_options const& opts, unsigned concurrency_hint);
252   261  
253   /** Create the blocking-I/O thread pool and apply only the decomposed 262   /** Create the blocking-I/O thread pool and apply only the decomposed
254   threading configuration (locking tiers), then finish bringing the 263   threading configuration (locking tiers), then finish bringing the
255   backend up. The tail of every plain constructor. Unlike the 264   backend up. The tail of every plain constructor. Unlike the
256   options constructors, it deliberately leaves the reactor budget 265   options constructors, it deliberately leaves the reactor budget
257   at its defaults rather than engaging the multi-thread 266   at its defaults rather than engaging the multi-thread
258   post-everything heuristic. */ 267   post-everything heuristic. */
259   void apply_threading_(io_context_options const& opts); 268   void apply_threading_(io_context_options const& opts);
260   269  
261   protected: 270   protected:
262   detail::scheduler* sched_; 271   detail::scheduler* sched_;
263   272  
264   // Resuming a chain installs its frame allocator on the running 273   // Resuming a chain installs its frame allocator on the running
265   // thread; a run call puts back what it found, so a coroutine created 274   // thread; a run call puts back what it found, so a coroutine created
266   // afterwards never reaches a resource that died with those frames. 275   // afterwards never reaches a resource that died with those frames.
267   struct frame_allocator_restore 276   struct frame_allocator_restore
268   { 277   {
269   std::pmr::memory_resource* saved = 278   std::pmr::memory_resource* saved =
270   capy::get_current_frame_allocator(); 279   capy::get_current_frame_allocator();
271   280  
HITCBC 272   11405 frame_allocator_restore() = default; 281   11225 frame_allocator_restore() = default;
273   frame_allocator_restore(frame_allocator_restore const&) = delete; 282   frame_allocator_restore(frame_allocator_restore const&) = delete;
274   frame_allocator_restore& 283   frame_allocator_restore&
275   operator=(frame_allocator_restore const&) = delete; 284   operator=(frame_allocator_restore const&) = delete;
276   285  
HITCBC 277   11405 ~frame_allocator_restore() 286   11225 ~frame_allocator_restore()
278   { 287   {
HITCBC 279   11405 capy::set_current_frame_allocator(saved); 288   11225 capy::set_current_frame_allocator(saved);
HITCBC 280   11405 } 289   11225 }
281   }; 290   };
282   291  
283   public: 292   public:
284   /** Dispatches and posts work to this context; see the 293   /** Dispatches and posts work to this context; see the
285   executor_type definition below. */ 294   executor_type definition below. */
286   class executor_type; 295   class executor_type;
287   296  
288   /** Construct with default concurrency and platform backend. 297   /** Construct with default concurrency and platform backend.
289   298  
290   Uses `std::thread::hardware_concurrency()` (floored to 1, in 299   Uses `std::thread::hardware_concurrency()` (floored to 1, in
291   case it reports 0) as the concurrency hint, and the default 300   case it reports 0) as the concurrency hint, and the default
292   @ref locking_mode::safe tier. Select a lockless tier via 301   @ref locking_mode::safe tier. Select a lockless tier via
293   @ref io_context_options::locking. 302   @ref io_context_options::locking.
294   303  
295   @throws std::system_error If the backend's infrastructure 304   @throws std::system_error If the backend's infrastructure
296   could not be created. 305   could not be created.
297   */ 306   */
298   io_context(); 307   io_context();
299   308  
300   /** Construct with a concurrency hint and platform backend. 309   /** Construct with a concurrency hint and platform backend.
301   310  
302   @param concurrency_hint Hint for the number of threads 311   @param concurrency_hint Hint for the number of threads
303   that calls `run()`. 312   that calls `run()`.
304   313  
305   @throws std::system_error If the backend's infrastructure 314   @throws std::system_error If the backend's infrastructure
306   could not be created. 315   could not be created.
307   */ 316   */
308   explicit io_context(unsigned concurrency_hint); 317   explicit io_context(unsigned concurrency_hint);
309   318  
310   /** Construct with runtime tuning options and platform backend. 319   /** Construct with runtime tuning options and platform backend.
311   320  
312   @param opts Runtime options controlling scheduler and 321   @param opts Runtime options controlling scheduler and
313   service behavior. 322   service behavior.
314   @param concurrency_hint Hint for the number of threads 323   @param concurrency_hint Hint for the number of threads
315   that calls `run()`. 324   that calls `run()`.
316   325  
317   @throws std::invalid_argument If `opts.thread_pool_size` is 326   @throws std::invalid_argument If `opts.thread_pool_size` is
318 - less than 1 (POSIX). 327 + less than 1 (POSIX), `opts.max_events_per_poll` is outside
  328 + the range its member documents, or `opts.inline_budget_max`
  329 + exceeds `INT_MAX` (platforms with a reactor backend).
319   330  
320   @throws std::system_error If the backend's infrastructure 331   @throws std::system_error If the backend's infrastructure
321   could not be created. 332   could not be created.
322   */ 333   */
323   explicit io_context( 334   explicit io_context(
324   io_context_options const& opts, 335   io_context_options const& opts,
325   unsigned concurrency_hint = std::thread::hardware_concurrency()); 336   unsigned concurrency_hint = std::thread::hardware_concurrency());
326   337  
327   /** Construct with an explicit backend tag. 338   /** Construct with an explicit backend tag.
328   339  
329   @tparam Backend A backend tag type that provides a static 340   @tparam Backend A backend tag type that provides a static
330   `construct(capy::execution_context&, unsigned)` factory 341   `construct(capy::execution_context&, unsigned)` factory
331   used to build the scheduler. 342   used to build the scheduler.
332   343  
333   @param backend The backend tag value selecting the I/O 344   @param backend The backend tag value selecting the I/O
334   multiplexer (e.g. `corosio::epoll`). 345   multiplexer (e.g. `corosio::epoll`).
335   @param concurrency_hint Hint for the number of threads 346   @param concurrency_hint Hint for the number of threads
336   that calls `run()`. 347   that calls `run()`.
337   348  
338   @throws std::system_error If the backend's infrastructure 349   @throws std::system_error If the backend's infrastructure
339   could not be created. 350   could not be created.
340   */ 351   */
341   template<class Backend> 352   template<class Backend>
342   requires requires { Backend::construct; } 353   requires requires { Backend::construct; }
HITCBC 343   3858 explicit io_context( 354   3934 explicit io_context(
344   [[maybe_unused]] Backend backend, 355   [[maybe_unused]] Backend backend,
345   unsigned concurrency_hint = std::thread::hardware_concurrency()) 356   unsigned concurrency_hint = std::thread::hardware_concurrency())
346   : capy::execution_context(this) 357   : capy::execution_context(this)
HITCBC 347   3858 , sched_(nullptr) 358   3934 , sched_(nullptr)
348   { 359   {
HITCBC 349   3858 sched_ = &Backend::construct(*this, concurrency_hint); 360   3934 sched_ = &Backend::construct(*this, concurrency_hint);
350   // Apply threading config only (locking tier). Unlike the options 361   // Apply threading config only (locking tier). Unlike the options
351   // ctor, the plain path leaves the reactor budget at its defaults. 362   // ctor, the plain path leaves the reactor budget at its defaults.
HITCBC 352   3842 apply_threading_(io_context_options{}); 363   3918 apply_threading_(io_context_options{});
HITCBC 353   3858 } 364   3934 }
354   365  
355   /** Construct with an explicit backend tag and runtime options. 366   /** Construct with an explicit backend tag and runtime options.
356   367  
357   @tparam Backend A backend tag type that provides a static 368   @tparam Backend A backend tag type that provides a static
358   `construct(capy::execution_context&, unsigned)` factory 369   `construct(capy::execution_context&, unsigned)` factory
359   used to build the scheduler. 370   used to build the scheduler.
360   371  
361   @param backend The backend tag value selecting the I/O 372   @param backend The backend tag value selecting the I/O
362   multiplexer (e.g. `corosio::epoll`). 373   multiplexer (e.g. `corosio::epoll`).
363   @param opts Runtime options controlling scheduler and 374   @param opts Runtime options controlling scheduler and
364   service behavior. 375   service behavior.
365   @param concurrency_hint Hint for the number of threads 376   @param concurrency_hint Hint for the number of threads
366   that calls `run()`. 377   that calls `run()`.
367   378  
368   @throws std::invalid_argument If `opts.thread_pool_size` is 379   @throws std::invalid_argument If `opts.thread_pool_size` is
369 - less than 1 (POSIX). 380 + less than 1 (POSIX), `opts.max_events_per_poll` is outside
  381 + the range its member documents, or `opts.inline_budget_max`
  382 + exceeds `INT_MAX` (platforms with a reactor backend).
370   383  
371   @throws std::system_error If the backend's infrastructure 384   @throws std::system_error If the backend's infrastructure
372   could not be created. 385   could not be created.
373   */ 386   */
374   template<class Backend> 387   template<class Backend>
375   requires requires { Backend::construct; } 388   requires requires { Backend::construct; }
HITCBC 376   45 explicit io_context( 389   53 explicit io_context(
377   [[maybe_unused]] Backend backend, 390   [[maybe_unused]] Backend backend,
378   io_context_options const& opts, 391   io_context_options const& opts,
379   unsigned concurrency_hint = std::thread::hardware_concurrency()) 392   unsigned concurrency_hint = std::thread::hardware_concurrency())
380   : capy::execution_context(this) 393   : capy::execution_context(this)
HITCBC 381   45 , sched_(nullptr) 394   53 , sched_(nullptr)
382   { 395   {
HITCBC 383   45 apply_options_pre_(opts); 396   53 apply_options_pre_(opts);
384   // Effective hint (1 for lockless tiers); see effective_concurrency_hint. 397   // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
385   unsigned const eff = 398   unsigned const eff =
HITCBC 386   45 detail::effective_concurrency_hint(opts, concurrency_hint); 399   41 detail::effective_concurrency_hint(opts, concurrency_hint);
HITCBC 387   45 sched_ = &Backend::construct(*this, eff); 400   41 sched_ = &Backend::construct(*this, eff);
HITCBC 388   45 apply_options_post_(opts, eff); 401   41 apply_options_post_(opts, eff);
HITCBC 389   45 } 402   53 }
390   403  
391   /** Destroy the context, its services and the coroutines it holds. 404   /** Destroy the context, its services and the coroutines it holds.
392   405  
393   The destructor stops the loop and destroys every service. Each 406   The destructor stops the loop and destroys every service. Each
394   coroutine suspended in an operation of this context, or posted to 407   coroutine suspended in an operation of this context, or posted to
395   it and not yet resumed, is abandoned. Its chain is unwound from 408   it and not yet resumed, is abandoned. Its chain is unwound from
396   the top, on the executor of the chain's owner (see 409   the top, on the executor of the chain's owner (see
397   `capy::abandon`). The precondition below makes that executor this 410   `capy::abandon`). The precondition below makes that executor this
398   context's, so the unwind runs during this destruction. 411   context's, so the unwind runs during this destruction.
399   412  
400   @pre No thread is executing a run variant. 413   @pre No thread is executing a run variant.
401   414  
402   @pre No work crosses into another execution context. A chain 415   @pre No work crosses into another execution context. A chain
403   held here must be owned on this context. No frame of a chain 416   held here must be owned on this context. No frame of a chain
404   owned elsewhere may own I/O objects of this context, or wait 417   owned elsewhere may own I/O objects of this context, or wait
405   on a primitive that a chain held here owns. Such a chain's 418   on a primitive that a chain held here owns. Such a chain's
406   unwind would run on the other context after this one is gone, 419   unwind would run on the other context after this one is gone,
407   whatever the order of destruction. Stop such work and run both 420   whatever the order of destruction. Stop such work and run both
408   contexts until it ends before destroying either. 421   contexts until it ends before destroying either.
409   422  
410   @note Every service gives up its work while the services shut 423   @note Every service gives up its work while the services shut
411   down, so nothing is left when they are destroyed. Work posted 424   down, so nothing is left when they are destroyed. Work posted
412   after that anyway, for example from a service's destructor, is 425   after that anyway, for example from a service's destructor, is
413   abandoned while the services are being destroyed. Its 426   abandoned while the services are being destroyed. Its
414   destructors must not use any service. 427   destructors must not use any service.
415   */ 428   */
416   ~io_context(); 429   ~io_context();
417   430  
418   /// Copy construction is disabled; the context owns its services. 431   /// Copy construction is disabled; the context owns its services.
419   io_context(io_context const&) = delete; 432   io_context(io_context const&) = delete;
420   /// Copy assignment is disabled; the context owns its services. 433   /// Copy assignment is disabled; the context owns its services.
421   io_context& operator=(io_context const&) = delete; 434   io_context& operator=(io_context const&) = delete;
422   435  
423   /** Return an executor for this context. 436   /** Return an executor for this context.
424   437  
425   The returned executor can be used to dispatch coroutines 438   The returned executor can be used to dispatch coroutines
426   and post work items to this context. 439   and post work items to this context.
427   440  
428   @return An executor associated with this context. 441   @return An executor associated with this context.
429   */ 442   */
430   executor_type get_executor() const noexcept; 443   executor_type get_executor() const noexcept;
431   444  
432   /** Signal the context to stop processing. 445   /** Signal the context to stop processing.
433   446  
434   This causes `run()` to return as soon as possible. Any pending 447   This causes `run()` to return as soon as possible. Any pending
435   work items remain queued. 448   work items remain queued.
  449 +
  450 + @throws std::system_error on Windows if the wakeup cannot be
  451 + queued, which leaves blocked `run()` callers unable to wake
  452 + until a later call succeeds.
436   */ 453   */
HITCBC 437   63 void stop() 454   63 void stop()
438   { 455   {
HITCBC 439   63 sched_->stop(); 456   63 sched_->stop();
HITCBC 440   63 } 457   63 }
441   458  
442   /** Return whether the context stopped. 459   /** Return whether the context stopped.
443   460  
444   @return `true` after a call to `stop()` with no later 461   @return `true` after a call to `stop()` with no later
445   call to `restart()`. 462   call to `restart()`.
446   */ 463   */
HITCBC 447   3526 bool stopped() const noexcept 464   3514 bool stopped() const noexcept
448   { 465   {
HITCBC 449   3526 return sched_->stopped(); 466   3514 return sched_->stopped();
450   } 467   }
451   468  
452   /** Restart the context after being stopped. 469   /** Restart the context after being stopped.
453   470  
454   This function must be called before `run()` can be called 471   This function must be called before `run()` can be called
455   again after a call to `stop()`. 472   again after a call to `stop()`.
456   */ 473   */
HITCBC 457   5551 void restart() 474   5344 void restart()
458   { 475   {
HITCBC 459   5551 sched_->restart(); 476   5344 sched_->restart();
HITCBC 460   5551 } 477   5344 }
461   478  
462   /** Process all pending work items. 479   /** Process all pending work items.
463   480  
464   This function blocks until it executes all pending work items, 481   This function blocks until it executes all pending work items,
465   or until `stop()` is called. The context is stopped 482   or until `stop()` is called. The context is stopped
466   when there is no more outstanding work. 483   when there is no more outstanding work.
467   484  
  485 + @throws std::system_error if waiting on the operating system's
  486 + event demultiplexer fails other than by interruption or
  487 + another transient condition; the context can no longer
  488 + dispatch work. On Windows, also if a wakeup needed to
  489 + stop the context cannot be queued.
  490 +
468   @note The context must be restarted with `restart()` before 491   @note The context must be restarted with `restart()` before
469   calling this function again after it returns. 492   calling this function again after it returns.
470   493  
471   @return The number of handlers executed. 494   @return The number of handlers executed.
472   */ 495   */
HITCBC 473   6206 std::size_t run() 496   6016 std::size_t run()
474   { 497   {
HITCBC 475   6206 frame_allocator_restore restore; 498   6016 frame_allocator_restore restore;
HITCBC 476   12409 return sched_->run(); 499   12029 return sched_->run();
HITCBC 477   6206 } 500   6016 }
478   501  
479   /** Process at most one pending work item. 502   /** Process at most one pending work item.
480   503  
481   This function blocks until it executes one work item 504   This function blocks until it executes one work item
482   or `stop()` is called. The context is stopped when there 505   or `stop()` is called. The context is stopped when there
483   is no more outstanding work. 506   is no more outstanding work.
484   507  
  508 + @throws std::system_error if waiting on the operating system's
  509 + event demultiplexer fails other than by interruption or
  510 + another transient condition; the context can no longer
  511 + dispatch work. On Windows, also if a wakeup needed to
  512 + stop the context cannot be queued.
  513 +
485   @note The context must be restarted with `restart()` before 514   @note The context must be restarted with `restart()` before
486   calling this function again after it returns. 515   calling this function again after it returns.
487   516  
488   @return The number of handlers executed (0 or 1). 517   @return The number of handlers executed (0 or 1).
489   */ 518   */
HITCBC 490   126 std::size_t run_one() 519   126 std::size_t run_one()
491   { 520   {
HITCBC 492   126 frame_allocator_restore restore; 521   126 frame_allocator_restore restore;
HITCBC 493   252 return sched_->run_one(); 522   252 return sched_->run_one();
HITCBC 494   126 } 523   126 }
495   524  
496   /** Process work items for the specified duration. 525   /** Process work items for the specified duration.
497   526  
498   This function blocks until it has executed work items for the 527   This function blocks until it has executed work items for the
499   specified duration, or until `stop()` is called. The context 528   specified duration, or until `stop()` is called. The context
500   is stopped when there is no more outstanding work. 529   is stopped when there is no more outstanding work.
501   530  
  531 + @throws std::system_error if waiting on the operating system's
  532 + event demultiplexer fails other than by interruption or
  533 + another transient condition; the context can no longer
  534 + dispatch work. On Windows, also if a wakeup needed to
  535 + stop the context cannot be queued.
  536 +
502   @note The context must be restarted with `restart()` before 537   @note The context must be restarted with `restart()` before
503   calling this function again after it returns. 538   calling this function again after it returns.
504   539  
505   @param rel_time The duration for which to process work. 540   @param rel_time The duration for which to process work.
506   541  
507   @return The number of handlers executed. 542   @return The number of handlers executed.
508   */ 543   */
509   template<class Rep, class Period> 544   template<class Rep, class Period>
HITCBC 510   827 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time) 545   827 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
511   { 546   {
HITCBC 512   827 return run_until(std::chrono::steady_clock::now() + rel_time); 547   827 return run_until(std::chrono::steady_clock::now() + rel_time);
513   } 548   }
514   549  
515   /** Process work items until the specified time. 550   /** Process work items until the specified time.
516   551  
517   This function blocks until the specified time is reached 552   This function blocks until the specified time is reached
518   or `stop()` is called. The context is stopped when there 553   or `stop()` is called. The context is stopped when there
519   is no more outstanding work. 554   is no more outstanding work.
520   555  
  556 + @throws std::system_error if waiting on the operating system's
  557 + event demultiplexer fails other than by interruption or
  558 + another transient condition; the context can no longer
  559 + dispatch work. On Windows, also if a wakeup needed to
  560 + stop the context cannot be queued.
  561 +
521   @note The context must be restarted with `restart()` before 562   @note The context must be restarted with `restart()` before
522   calling this function again after it returns. 563   calling this function again after it returns.
523   564  
524   @param abs_time The time point until which to process work. 565   @param abs_time The time point until which to process work.
525   566  
526   @return The number of handlers executed. 567   @return The number of handlers executed.
527   */ 568   */
528   template<class Clock, class Duration> 569   template<class Clock, class Duration>
529   std::size_t 570   std::size_t
HITCBC 530   828 run_until(std::chrono::time_point<Clock, Duration> const& abs_time) 571   828 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
531   { 572   {
HITCBC 532   828 std::size_t n = 0; 573   828 std::size_t n = 0;
HITCBC 533   2459 while (run_one_until(abs_time)) 574   2448 while (run_one_until(abs_time))
HITCBC 534   1631 if (n != (std::numeric_limits<std::size_t>::max)()) 575   1620 if (n != (std::numeric_limits<std::size_t>::max)())
HITCBC 535   1631 ++n; 576   1620 ++n;
HITCBC 536   828 return n; 577   828 return n;
537   } 578   }
538   579  
539   /** Process at most one work item for the specified duration. 580   /** Process at most one work item for the specified duration.
540   581  
541   This function blocks until it executes one work item, 582   This function blocks until it executes one work item,
542   the specified duration has elapsed, or `stop()` is called. 583   the specified duration has elapsed, or `stop()` is called.
543   The context is stopped when there is no more outstanding work. 584   The context is stopped when there is no more outstanding work.
544   585  
  586 + @throws std::system_error if waiting on the operating system's
  587 + event demultiplexer fails other than by interruption or
  588 + another transient condition; the context can no longer
  589 + dispatch work. On Windows, also if a wakeup needed to
  590 + stop the context cannot be queued.
  591 +
545   @note The context must be restarted with `restart()` before 592   @note The context must be restarted with `restart()` before
546   calling this function again after it returns. 593   calling this function again after it returns.
547   594  
548   @param rel_time The duration for which the call may block. 595   @param rel_time The duration for which the call may block.
549   596  
550   @return The number of handlers executed (0 or 1). 597   @return The number of handlers executed (0 or 1).
551   */ 598   */
552   template<class Rep, class Period> 599   template<class Rep, class Period>
HITCBC 553   1070 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time) 600   1071 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
554   { 601   {
HITCBC 555   1070 return run_one_until(std::chrono::steady_clock::now() + rel_time); 602   1071 return run_one_until(std::chrono::steady_clock::now() + rel_time);
556   } 603   }
557   604  
558   /** Process at most one work item until the specified time. 605   /** Process at most one work item until the specified time.
559   606  
560   This function blocks until it executes one work item, 607   This function blocks until it executes one work item,
561   the specified time is reached, or `stop()` is called. 608   the specified time is reached, or `stop()` is called.
562   The context is stopped when there is no more outstanding work. 609   The context is stopped when there is no more outstanding work.
563   610  
  611 + @throws std::system_error if waiting on the operating system's
  612 + event demultiplexer fails other than by interruption or
  613 + another transient condition; the context can no longer
  614 + dispatch work. On Windows, also if a wakeup needed to
  615 + stop the context cannot be queued.
  616 +
564   @note The context must be restarted with `restart()` before 617   @note The context must be restarted with `restart()` before
565   calling this function again after it returns. 618   calling this function again after it returns.
566   619  
567   @param abs_time The time point until which the call may block. 620   @param abs_time The time point until which the call may block.
568   621  
569   @return The number of handlers executed (0 or 1). 622   @return The number of handlers executed (0 or 1).
570   */ 623   */
571   template<class Clock, class Duration> 624   template<class Clock, class Duration>
572   std::size_t 625   std::size_t
HITCBC 573   3537 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time) 626   3527 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
574   { 627   {
HITCBC 575   3537 frame_allocator_restore restore; 628   3527 frame_allocator_restore restore;
HITCBC 576   3537 typename Clock::time_point now = Clock::now(); 629   3527 typename Clock::time_point now = Clock::now();
HITCBC 577   2602 for (;;) 630   2590 for (;;)
578   { 631   {
HITCBC 579   6139 auto rel_time = abs_time - now; 632   6117 auto rel_time = abs_time - now;
580   using rel_type = decltype(rel_time); 633   using rel_type = decltype(rel_time);
HITCBC 581   6139 if (rel_time < rel_type::zero()) 634   6117 if (rel_time < rel_type::zero())
HITCBC 582   5 rel_time = rel_type::zero(); 635   5 rel_time = rel_type::zero();
HITCBC 583   6134 else if (rel_time > std::chrono::seconds(1)) 636   6112 else if (rel_time > std::chrono::seconds(1))
HITCBC 584   5946 rel_time = std::chrono::seconds(1); 637   5925 rel_time = std::chrono::seconds(1);
585   638  
HITCBC 586   6139 std::size_t s = sched_->wait_one( 639   6117 std::size_t s = sched_->wait_one(
587   static_cast<long>( 640   static_cast<long>(
HITCBC 588   6139 std::chrono::duration_cast<std::chrono::microseconds>( 641   6117 std::chrono::duration_cast<std::chrono::microseconds>(
589   rel_time) 642   rel_time)
HITCBC 590   6139 .count())); 643   6117 .count()));
591   644  
HITCBC 592   6139 if (s || stopped()) 645   6117 if (s || stopped())
HITCBC 593   3501 return s; 646   3491 return s;
594   647  
HITCBC 595   2638 now = Clock::now(); 648   2626 now = Clock::now();
HITCBC 596   2638 if (now >= abs_time) 649   2626 if (now >= abs_time)
HITCBC 597   36 return 0; 650   36 return 0;
598   } 651   }
HITCBC 599   3537 } 652   3527 }
600   653  
601   /** Process all ready work items without blocking. 654   /** Process all ready work items without blocking.
602   655  
603   This function executes all work items that are ready to run 656   This function executes all work items that are ready to run
604   without blocking for more work. The context is stopped 657   without blocking for more work. The context is stopped
605   when there is no more outstanding work. 658   when there is no more outstanding work.
606   659  
  660 + @throws std::system_error if waiting on the operating system's
  661 + event demultiplexer fails other than by interruption or
  662 + another transient condition; the context can no longer
  663 + dispatch work. On Windows, also if a wakeup needed to
  664 + stop the context cannot be queued.
  665 +
607   @note The context must be restarted with `restart()` before 666   @note The context must be restarted with `restart()` before
608   calling this function again after it returns. 667   calling this function again after it returns.
609   668  
610   @return The number of handlers executed. 669   @return The number of handlers executed.
611   */ 670   */
HITCBC 612   1327 std::size_t poll() 671   1327 std::size_t poll()
613   { 672   {
HITCBC 614   1327 frame_allocator_restore restore; 673   1327 frame_allocator_restore restore;
HITCBC 615   2654 return sched_->poll(); 674   2654 return sched_->poll();
HITCBC 616   1327 } 675   1327 }
617   676  
618   /** Process at most one ready work item without blocking. 677   /** Process at most one ready work item without blocking.
619   678  
620   This function executes at most one work item that is ready 679   This function executes at most one work item that is ready
621   to run without blocking for more work. The context is 680   to run without blocking for more work. The context is
622   stopped when there is no more outstanding work. 681   stopped when there is no more outstanding work.
  682 +
  683 + @throws std::system_error if waiting on the operating system's
  684 + event demultiplexer fails other than by interruption or
  685 + another transient condition; the context can no longer
  686 + dispatch work. On Windows, also if a wakeup needed to
  687 + stop the context cannot be queued.
623   688  
624   @note The context must be restarted with `restart()` before 689   @note The context must be restarted with `restart()` before
625   calling this function again after it returns. 690   calling this function again after it returns.
626   691  
627   @return The number of handlers executed (0 or 1). 692   @return The number of handlers executed (0 or 1).
628   */ 693   */
HITCBC 629   58 std::size_t poll_one() 694   58 std::size_t poll_one()
630   { 695   {
HITCBC 631   58 frame_allocator_restore restore; 696   58 frame_allocator_restore restore;
HITCBC 632   116 return sched_->poll_one(); 697   116 return sched_->poll_one();
HITCBC 633   58 } 698   58 }
634   }; 699   };
635   700  
636   /** Dispatches and posts work to an I/O context. 701   /** Dispatches and posts work to an I/O context.
637   702  
638   The executor provides the interface for posting work items and 703   The executor provides the interface for posting work items and
639   dispatching coroutines to the associated context. It satisfies 704   dispatching coroutines to the associated context. It satisfies
640   the `capy::Executor` concept. 705   the `capy::Executor` concept.
641   706  
642   Executors are lightweight handles that can be copied and compared 707   Executors are lightweight handles that can be copied and compared
643   for equality. Two executors compare equal if they refer to the 708   for equality. Two executors compare equal if they refer to the
644   same context. 709   same context.
645   710  
646   @par Thread Safety 711   @par Thread Safety
647   Distinct objects: Safe.@n 712   Distinct objects: Safe.@n
648   Shared objects: Safe. 713   Shared objects: Safe.
649   */ 714   */
650   class io_context::executor_type 715   class io_context::executor_type
651   { 716   {
652   io_context* ctx_ = nullptr; 717   io_context* ctx_ = nullptr;
653   718  
654   public: 719   public:
655   /** Constructs an executor not associated with any context. */ 720   /** Constructs an executor not associated with any context. */
HITCBC 656   2057 executor_type() = default; 721   2057 executor_type() = default;
657   722  
658   /** Construct an executor from a context. 723   /** Construct an executor from a context.
659   724  
660   @param ctx The context to associate with this executor. 725   @param ctx The context to associate with this executor.
661   */ 726   */
HITCBC 662   18076 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {} 727   17872 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
663   728  
664   /** Return a reference to the associated execution context. 729   /** Return a reference to the associated execution context.
665   730  
666   @return Reference to the context. 731   @return Reference to the context.
667   */ 732   */
HITCBC 668   46311 io_context& context() const noexcept 733   46097 io_context& context() const noexcept
669   { 734   {
HITCBC 670   46311 return *ctx_; 735   46097 return *ctx_;
671   } 736   }
672   737  
673   /** Check if the current thread is running this executor's context. 738   /** Check if the current thread is running this executor's context.
674   739  
675   @return `true` if `run()` is being called on this thread. 740   @return `true` if `run()` is being called on this thread.
676   */ 741   */
HITCBC 677   478913 bool running_in_this_thread() const noexcept 742   478360 bool running_in_this_thread() const noexcept
678   { 743   {
679   // The innermost scheduler answers the common case without a 744   // The innermost scheduler answers the common case without a
680   // virtual call or a frame walk. 745   // virtual call or a frame walk.
HITCBC 681   502485 return detail::running_scheduler.get() == ctx_->sched_ || 746   501792 return detail::running_scheduler.get() == ctx_->sched_ ||
HITCBC 682   502485 ctx_->sched_->running_in_this_thread(); 747   501792 ctx_->sched_->running_in_this_thread();
683   } 748   }
684   749  
685   /** Informs the executor that work is beginning. 750   /** Informs the executor that work is beginning.
686   751  
687   Must be paired with `on_work_finished()`. 752   Must be paired with `on_work_finished()`.
688   */ 753   */
HITCBC 689   24917 void on_work_started() const noexcept 754   24710 void on_work_started() const noexcept
690   { 755   {
HITCBC 691   24917 ctx_->sched_->work_started(); 756   24710 ctx_->sched_->work_started();
HITCBC 692   24917 } 757   24710 }
693   758  
694   /** Informs the executor that work has completed. 759   /** Informs the executor that work has completed.
695   760  
696   @pre A preceding call to `on_work_started()` on an equal executor. 761   @pre A preceding call to `on_work_started()` on an equal executor.
697   */ 762   */
HITCBC 698   24917 void on_work_finished() const noexcept 763   24710 void on_work_finished() const noexcept
699   { 764   {
HITCBC 700   24917 ctx_->sched_->work_finished(); 765   24710 ctx_->sched_->work_finished();
HITCBC 701   24917 } 766   24710 }
702   767  
703   /** Dispatch a continuation. 768   /** Dispatch a continuation.
704   769  
705   Returns a handle for symmetric transfer. If called from 770   Returns a handle for symmetric transfer. If called from
706   within `run()`, returns `c.h`. Otherwise posts `c` for 771   within `run()`, returns `c.h`. Otherwise posts `c` for
707   later execution and returns `std::noop_coroutine()`. 772   later execution and returns `std::noop_coroutine()`.
708   773  
709   @param c The continuation to dispatch. 774   @param c The continuation to dispatch.
710   775  
711   @return A handle for symmetric transfer or `std::noop_coroutine()`. 776   @return A handle for symmetric transfer or `std::noop_coroutine()`.
712   777  
713   @pre The associated context must outlive this call. Dispatching 778   @pre The associated context must outlive this call. Dispatching
714   concurrently with, or after, the context's destruction is 779   concurrently with, or after, the context's destruction is
715   undefined behavior. 780   undefined behavior.
716   */ 781   */
HITCBC 717   478906 std::coroutine_handle<> dispatch(capy::continuation& c) const 782   478353 std::coroutine_handle<> dispatch(capy::continuation& c) const
718   { 783   {
HITCBC 719   478906 if (running_in_this_thread()) 784   478353 if (running_in_this_thread())
HITCBC 720   455337 return c.h; 785   454924 return c.h;
HITCBC 721   23569 post(c); 786   23429 post(c);
HITCBC 722   23569 return std::noop_coroutine(); 787   23429 return std::noop_coroutine();
723   } 788   }
724   789  
725   /** Post a continuation for deferred execution. 790   /** Post a continuation for deferred execution.
726   791  
727   Enqueues `c` directly on the scheduler's ready queue. 792   Enqueues `c` directly on the scheduler's ready queue.
728   No heap allocation occurs. 793   No heap allocation occurs.
729   794  
730   @param c The continuation to enqueue. 795   @param c The continuation to enqueue.
731   796  
732   @pre The associated context must outlive this call. Posting 797   @pre The associated context must outlive this call. Posting
733   concurrently with, or after, the context's destruction is 798   concurrently with, or after, the context's destruction is
734   undefined behavior. 799   undefined behavior.
735   */ 800   */
HITCBC 736   236493 void post(capy::continuation& c) const 801   236244 void post(capy::continuation& c) const
737   { 802   {
HITCBC 738   236493 ctx_->sched_->post(c); 803   236244 ctx_->sched_->post(c);
HITCBC 739   236493 } 804   236244 }
740   805  
741   /** Post a bare coroutine handle for deferred execution. 806   /** Post a bare coroutine handle for deferred execution.
742   807  
743   Heap-allocates a `scheduler_op` to wrap the handle. A caller 808   Heap-allocates a `scheduler_op` to wrap the handle. A caller
744   that already owns a `capy::continuation` can post it directly 809   that already owns a `capy::continuation` can post it directly
745   via the `post(capy::continuation&)` overload to avoid the 810   via the `post(capy::continuation&)` overload to avoid the
746   allocation. 811   allocation.
747   812  
748   A handle still queued when the context is torn down is 813   A handle still queued when the context is torn down is
749   destroyed alone, since it carries no owner to unwind its chain 814   destroyed alone, since it carries no owner to unwind its chain
750   from. Post a coroutine that another frame awaits through the 815   from. Post a coroutine that another frame awaits through the
751   `post(capy::continuation&)` overload, with its owner set. 816   `post(capy::continuation&)` overload, with its owner set.
752   817  
753   @param h The coroutine handle to post. 818   @param h The coroutine handle to post.
754   819  
755   @pre The associated context must outlive this call. Posting 820   @pre The associated context must outlive this call. Posting
756   concurrently with, or after, the context's destruction is 821   concurrently with, or after, the context's destruction is
757   undefined behavior. 822   undefined behavior.
758   823  
759   @pre Nothing awaits `h`. 824   @pre Nothing awaits `h`.
760   */ 825   */
HITCBC 761   3672 void post(std::coroutine_handle<> h) const 826   3672 void post(std::coroutine_handle<> h) const
762   { 827   {
HITCBC 763   3672 ctx_->sched_->post(h); 828   3672 ctx_->sched_->post(h);
HITCBC 764   3672 } 829   3672 }
765   830  
766   /** Compare two executors for equality. 831   /** Compare two executors for equality.
767   832  
768   @return `true` if both executors refer to the same context. 833   @return `true` if both executors refer to the same context.
769   */ 834   */
HITCBC 770   2 bool operator==(executor_type const& other) const noexcept 835   2 bool operator==(executor_type const& other) const noexcept
771   { 836   {
HITCBC 772   2 return ctx_ == other.ctx_; 837   2 return ctx_ == other.ctx_;
773   } 838   }
774   839  
775   /** Compare two executors for inequality. 840   /** Compare two executors for inequality.
776   841  
777   @return `true` if the executors refer to different contexts. 842   @return `true` if the executors refer to different contexts.
778   */ 843   */
779   bool operator!=(executor_type const& other) const noexcept 844   bool operator!=(executor_type const& other) const noexcept
780   { 845   {
781   return ctx_ != other.ctx_; 846   return ctx_ != other.ctx_;
782   } 847   }
783   }; 848   };
784   849  
785   inline io_context::executor_type 850   inline io_context::executor_type
HITCBC 786   18076 io_context::get_executor() const noexcept 851   17872 io_context::get_executor() const noexcept
787   { 852   {
HITCBC 788   18076 return executor_type(const_cast<io_context&>(*this)); 853   17872 return executor_type(const_cast<io_context&>(*this));
789   } 854   }
790   855  
791   } // namespace boost::corosio 856   } // namespace boost::corosio
792   857  
793   #endif // BOOST_COROSIO_IO_CONTEXT_HPP 858   #endif // BOOST_COROSIO_IO_CONTEXT_HPP