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