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