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   11682 frame_allocator_restore() = default; 272   11490 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   11682 ~frame_allocator_restore() 277   11490 ~frame_allocator_restore()
278   { 278   {
HITCBC 279   11682 capy::set_current_frame_allocator(saved); 279   11490 capy::set_current_frame_allocator(saved);
HITCBC 280   11682 } 280   11490 }
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   3841 explicit io_context( 343   3841 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   3841 , sched_(nullptr) 347   3841 , sched_(nullptr)
348   { 348   {
HITCBC 349   3841 sched_ = &Backend::construct(*this, concurrency_hint); 349   3841 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   3829 apply_threading_(io_context_options{}); 352   3829 apply_threading_(io_context_options{});
HITCBC 353   3841 } 353   3841 }
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 Every service gives up its work while the services shut
411 - from a service's destructor, is abandoned while the services 411 + down, so nothing is left when they are destroyed. Work posted
412 - are being destroyed. Its destructors must not use them. 412 + after that anyway, for example from a service's destructor, is
  413 + abandoned while the services are being destroyed. Its
  414 + destructors must not use any service.
413   */ 415   */
414   ~io_context(); 416   ~io_context();
415   417  
416   /// Copy construction is disabled; the context owns its services. 418   /// Copy construction is disabled; the context owns its services.
417   io_context(io_context const&) = delete; 419   io_context(io_context const&) = delete;
418   /// Copy assignment is disabled; the context owns its services. 420   /// Copy assignment is disabled; the context owns its services.
419   io_context& operator=(io_context const&) = delete; 421   io_context& operator=(io_context const&) = delete;
420   422  
421   /** Return an executor for this context. 423   /** Return an executor for this context.
422   424  
423   The returned executor can be used to dispatch coroutines 425   The returned executor can be used to dispatch coroutines
424   and post work items to this context. 426   and post work items to this context.
425   427  
426   @return An executor associated with this context. 428   @return An executor associated with this context.
427   */ 429   */
428   executor_type get_executor() const noexcept; 430   executor_type get_executor() const noexcept;
429   431  
430   /** Signal the context to stop processing. 432   /** Signal the context to stop processing.
431   433  
432   This causes `run()` to return as soon as possible. Any pending 434   This causes `run()` to return as soon as possible. Any pending
433   work items remain queued. 435   work items remain queued.
434   */ 436   */
HITCBC 435   63 void stop() 437   63 void stop()
436   { 438   {
HITCBC 437   63 sched_->stop(); 439   63 sched_->stop();
HITCBC 438   63 } 440   63 }
439   441  
440   /** Return whether the context stopped. 442   /** Return whether the context stopped.
441   443  
442   @return `true` after a call to `stop()` with no later 444   @return `true` after a call to `stop()` with no later
443   call to `restart()`. 445   call to `restart()`.
444   */ 446   */
HITCBC 445   3525 bool stopped() const noexcept 447   3527 bool stopped() const noexcept
446   { 448   {
HITCBC 447   3525 return sched_->stopped(); 449   3527 return sched_->stopped();
448   } 450   }
449   451  
450   /** Restart the context after being stopped. 452   /** Restart the context after being stopped.
451   453  
452   This function must be called before `run()` can be called 454   This function must be called before `run()` can be called
453   again after a call to `stop()`. 455   again after a call to `stop()`.
454   */ 456   */
HITCBC 455   5829 void restart() 457   5616 void restart()
456   { 458   {
HITCBC 457   5829 sched_->restart(); 459   5616 sched_->restart();
HITCBC 458   5829 } 460   5616 }
459   461  
460   /** Process all pending work items. 462   /** Process all pending work items.
461   463  
462   This function blocks until it executes all pending work items, 464   This function blocks until it executes all pending work items,
463   or until `stop()` is called. The context is stopped 465   or until `stop()` is called. The context is stopped
464   when there is no more outstanding work. 466   when there is no more outstanding work.
465   467  
466   @note The context must be restarted with `restart()` before 468   @note The context must be restarted with `restart()` before
467   calling this function again after it returns. 469   calling this function again after it returns.
468   470  
469   @return The number of handlers executed. 471   @return The number of handlers executed.
470   */ 472   */
HITCBC 471   6488 std::size_t run() 473   6292 std::size_t run()
472   { 474   {
HITCBC 473   6488 frame_allocator_restore restore; 475   6292 frame_allocator_restore restore;
HITCBC 474   12973 return sched_->run(); 476   12581 return sched_->run();
HITCBC 475   6488 } 477   6292 }
476   478  
477   /** Process at most one pending work item. 479   /** Process at most one pending work item.
478   480  
479   This function blocks until it executes one work item 481   This function blocks until it executes one work item
480   or `stop()` is called. The context is stopped when there 482   or `stop()` is called. The context is stopped when there
481   is no more outstanding work. 483   is no more outstanding work.
482   484  
483   @note The context must be restarted with `restart()` before 485   @note The context must be restarted with `restart()` before
484   calling this function again after it returns. 486   calling this function again after it returns.
485   487  
486   @return The number of handlers executed (0 or 1). 488   @return The number of handlers executed (0 or 1).
487   */ 489   */
HITCBC 488   128 std::size_t run_one() 490   128 std::size_t run_one()
489   { 491   {
HITCBC 490   128 frame_allocator_restore restore; 492   128 frame_allocator_restore restore;
HITCBC 491   256 return sched_->run_one(); 493   256 return sched_->run_one();
HITCBC 492   128 } 494   128 }
493   495  
494   /** Process work items for the specified duration. 496   /** Process work items for the specified duration.
495   497  
496   This function blocks until it has executed work items for the 498   This function blocks until it has executed work items for the
497   specified duration, or until `stop()` is called. The context 499   specified duration, or until `stop()` is called. The context
498   is stopped when there is no more outstanding work. 500   is stopped when there is no more outstanding work.
499   501  
500   @note The context must be restarted with `restart()` before 502   @note The context must be restarted with `restart()` before
501   calling this function again after it returns. 503   calling this function again after it returns.
502   504  
503   @param rel_time The duration for which to process work. 505   @param rel_time The duration for which to process work.
504   506  
505   @return The number of handlers executed. 507   @return The number of handlers executed.
506   */ 508   */
507   template<class Rep, class Period> 509   template<class Rep, class Period>
HITCBC 508   827 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time) 510   825 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
509   { 511   {
HITCBC 510   827 return run_until(std::chrono::steady_clock::now() + rel_time); 512   825 return run_until(std::chrono::steady_clock::now() + rel_time);
511   } 513   }
512   514  
513   /** Process work items until the specified time. 515   /** Process work items until the specified time.
514   516  
515   This function blocks until the specified time is reached 517   This function blocks until the specified time is reached
516   or `stop()` is called. The context is stopped when there 518   or `stop()` is called. The context is stopped when there
517   is no more outstanding work. 519   is no more outstanding work.
518   520  
519   @note The context must be restarted with `restart()` before 521   @note The context must be restarted with `restart()` before
520   calling this function again after it returns. 522   calling this function again after it returns.
521   523  
522   @param abs_time The time point until which to process work. 524   @param abs_time The time point until which to process work.
523   525  
524   @return The number of handlers executed. 526   @return The number of handlers executed.
525   */ 527   */
526   template<class Clock, class Duration> 528   template<class Clock, class Duration>
527   std::size_t 529   std::size_t
HITCBC 528   828 run_until(std::chrono::time_point<Clock, Duration> const& abs_time) 530   826 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
529   { 531   {
HITCBC 530   828 std::size_t n = 0; 532   826 std::size_t n = 0;
HITCBC 531   2457 while (run_one_until(abs_time)) 533   2455 while (run_one_until(abs_time))
HITCBC 532   1629 if (n != (std::numeric_limits<std::size_t>::max)()) 534   1629 if (n != (std::numeric_limits<std::size_t>::max)())
HITCBC 533   1629 ++n; 535   1629 ++n;
HITCBC 534   828 return n; 536   826 return n;
535   } 537   }
536   538  
537   /** Process at most one work item for the specified duration. 539   /** Process at most one work item for the specified duration.
538   540  
539   This function blocks until it executes one work item, 541   This function blocks until it executes one work item,
540   the specified duration has elapsed, or `stop()` is called. 542   the specified duration has elapsed, or `stop()` is called.
541   The context is stopped when there is no more outstanding work. 543   The context is stopped when there is no more outstanding work.
542   544  
543   @note The context must be restarted with `restart()` before 545   @note The context must be restarted with `restart()` before
544   calling this function again after it returns. 546   calling this function again after it returns.
545   547  
546   @param rel_time The duration for which the call may block. 548   @param rel_time The duration for which the call may block.
547   549  
548   @return The number of handlers executed (0 or 1). 550   @return The number of handlers executed (0 or 1).
549   */ 551   */
550   template<class Rep, class Period> 552   template<class Rep, class Period>
HITCBC 551   1071 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time) 553   1075 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
552   { 554   {
HITCBC 553   1071 return run_one_until(std::chrono::steady_clock::now() + rel_time); 555   1075 return run_one_until(std::chrono::steady_clock::now() + rel_time);
554   } 556   }
555   557  
556   /** Process at most one work item until the specified time. 558   /** Process at most one work item until the specified time.
557   559  
558   This function blocks until it executes one work item, 560   This function blocks until it executes one work item,
559   the specified time is reached, or `stop()` is called. 561   the specified time is reached, or `stop()` is called.
560   The context is stopped when there is no more outstanding work. 562   The context is stopped when there is no more outstanding work.
561   563  
562   @note The context must be restarted with `restart()` before 564   @note The context must be restarted with `restart()` before
563   calling this function again after it returns. 565   calling this function again after it returns.
564   566  
565   @param abs_time The time point until which the call may block. 567   @param abs_time The time point until which the call may block.
566   568  
567   @return The number of handlers executed (0 or 1). 569   @return The number of handlers executed (0 or 1).
568   */ 570   */
569   template<class Clock, class Duration> 571   template<class Clock, class Duration>
570   std::size_t 572   std::size_t
HITCBC 571   3536 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time) 573   3538 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
572   { 574   {
HITCBC 573   3536 frame_allocator_restore restore; 575   3538 frame_allocator_restore restore;
HITCBC 574   3536 typename Clock::time_point now = Clock::now(); 576   3538 typename Clock::time_point now = Clock::now();
HITCBC 575   2601 for (;;) 577   2605 for (;;)
576   { 578   {
HITCBC 577   6137 auto rel_time = abs_time - now; 579   6143 auto rel_time = abs_time - now;
578   using rel_type = decltype(rel_time); 580   using rel_type = decltype(rel_time);
HITCBC 579   6137 if (rel_time < rel_type::zero()) 581   6143 if (rel_time < rel_type::zero())
HITCBC 580   5 rel_time = rel_type::zero(); 582   5 rel_time = rel_type::zero();
HITCBC 581   6132 else if (rel_time > std::chrono::seconds(1)) 583   6138 else if (rel_time > std::chrono::seconds(1))
HITCBC 582   5944 rel_time = std::chrono::seconds(1); 584   5949 rel_time = std::chrono::seconds(1);
583   585  
HITCBC 584   6137 std::size_t s = sched_->wait_one( 586   6143 std::size_t s = sched_->wait_one(
585   static_cast<long>( 587   static_cast<long>(
HITCBC 586   6137 std::chrono::duration_cast<std::chrono::microseconds>( 588   6143 std::chrono::duration_cast<std::chrono::microseconds>(
587   rel_time) 589   rel_time)
HITCBC 588   6137 .count())); 590   6143 .count()));
589   591  
HITCBC 590   6137 if (s || stopped()) 592   6143 if (s || stopped())
HITCBC 591   3500 return s; 593   3502 return s;
592   594  
HITCBC 593   2637 now = Clock::now(); 595   2641 now = Clock::now();
HITCBC 594   2637 if (now >= abs_time) 596   2641 if (now >= abs_time)
HITCBC 595   36 return 0; 597   36 return 0;
596   } 598   }
HITCBC 597   3536 } 599   3538 }
598   600  
599   /** Process all ready work items without blocking. 601   /** Process all ready work items without blocking.
600   602  
601   This function executes all work items that are ready to run 603   This function executes all work items that are ready to run
602   without blocking for more work. The context is stopped 604   without blocking for more work. The context is stopped
603   when there is no more outstanding work. 605   when there is no more outstanding work.
604   606  
605   @note The context must be restarted with `restart()` before 607   @note The context must be restarted with `restart()` before
606   calling this function again after it returns. 608   calling this function again after it returns.
607   609  
608   @return The number of handlers executed. 610   @return The number of handlers executed.
609   */ 611   */
HITCBC 610   1323 std::size_t poll() 612   1325 std::size_t poll()
611   { 613   {
HITCBC 612   1323 frame_allocator_restore restore; 614   1325 frame_allocator_restore restore;
HITCBC 613   2646 return sched_->poll(); 615   2650 return sched_->poll();
HITCBC 614   1323 } 616   1325 }
615   617  
616   /** Process at most one ready work item without blocking. 618   /** Process at most one ready work item without blocking.
617   619  
618   This function executes at most one work item that is ready 620   This function executes at most one work item that is ready
619   to run without blocking for more work. The context is 621   to run without blocking for more work. The context is
620   stopped when there is no more outstanding work. 622   stopped when there is no more outstanding work.
621   623  
622   @note The context must be restarted with `restart()` before 624   @note The context must be restarted with `restart()` before
623   calling this function again after it returns. 625   calling this function again after it returns.
624   626  
625   @return The number of handlers executed (0 or 1). 627   @return The number of handlers executed (0 or 1).
626   */ 628   */
HITCBC 627   56 std::size_t poll_one() 629   56 std::size_t poll_one()
628   { 630   {
HITCBC 629   56 frame_allocator_restore restore; 631   56 frame_allocator_restore restore;
HITCBC 630   112 return sched_->poll_one(); 632   112 return sched_->poll_one();
HITCBC 631   56 } 633   56 }
632   }; 634   };
633   635  
634   /** Dispatches and posts work to an I/O context. 636   /** Dispatches and posts work to an I/O context.
635   637  
636   The executor provides the interface for posting work items and 638   The executor provides the interface for posting work items and
637   dispatching coroutines to the associated context. It satisfies 639   dispatching coroutines to the associated context. It satisfies
638   the `capy::Executor` concept. 640   the `capy::Executor` concept.
639   641  
640   Executors are lightweight handles that can be copied and compared 642   Executors are lightweight handles that can be copied and compared
641   for equality. Two executors compare equal if they refer to the 643   for equality. Two executors compare equal if they refer to the
642   same context. 644   same context.
643   645  
644   @par Thread Safety 646   @par Thread Safety
645   Distinct objects: Safe.@n 647   Distinct objects: Safe.@n
646   Shared objects: Safe. 648   Shared objects: Safe.
647   */ 649   */
648   class io_context::executor_type 650   class io_context::executor_type
649   { 651   {
650   io_context* ctx_ = nullptr; 652   io_context* ctx_ = nullptr;
651   653  
652   public: 654   public:
653   /** Constructs an executor not associated with any context. */ 655   /** Constructs an executor not associated with any context. */
HITCBC 654   2057 executor_type() = default; 656   2057 executor_type() = default;
655   657  
656   /** Construct an executor from a context. 658   /** Construct an executor from a context.
657   659  
658   @param ctx The context to associate with this executor. 660   @param ctx The context to associate with this executor.
659   */ 661   */
HITCBC 660   18359 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {} 662   18077 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
661   663  
662   /** Return a reference to the associated execution context. 664   /** Return a reference to the associated execution context.
663   665  
664   @return Reference to the context. 666   @return Reference to the context.
665   */ 667   */
HITCBC 666   45882 io_context& context() const noexcept 668   46105 io_context& context() const noexcept
667   { 669   {
HITCBC 668   45882 return *ctx_; 670   46105 return *ctx_;
669   } 671   }
670   672  
671   /** Check if the current thread is running this executor's context. 673   /** Check if the current thread is running this executor's context.
672   674  
673   @return `true` if `run()` is being called on this thread. 675   @return `true` if `run()` is being called on this thread.
674   */ 676   */
HITCBC 675   493072 bool running_in_this_thread() const noexcept 677   505806 bool running_in_this_thread() const noexcept
676   { 678   {
677   // The innermost scheduler answers the common case without a 679   // The innermost scheduler answers the common case without a
678   // virtual call or a frame walk. 680   // virtual call or a frame walk.
HITCBC 679   516906 return detail::running_scheduler.get() == ctx_->sched_ || 681   529462 return detail::running_scheduler.get() == ctx_->sched_ ||
HITCBC 680   516906 ctx_->sched_->running_in_this_thread(); 682   529462 ctx_->sched_->running_in_this_thread();
681   } 683   }
682   684  
683   /** Informs the executor that work is beginning. 685   /** Informs the executor that work is beginning.
684   686  
685   Must be paired with `on_work_finished()`. 687   Must be paired with `on_work_finished()`.
686   */ 688   */
HITCBC 687   25220 void on_work_started() const noexcept 689   24959 void on_work_started() const noexcept
688   { 690   {
HITCBC 689   25220 ctx_->sched_->work_started(); 691   24959 ctx_->sched_->work_started();
HITCBC 690   25220 } 692   24959 }
691   693  
692   /** Informs the executor that work has completed. 694   /** Informs the executor that work has completed.
693   695  
694   @pre A preceding call to `on_work_started()` on an equal executor. 696   @pre A preceding call to `on_work_started()` on an equal executor.
695   */ 697   */
HITCBC 696   25220 void on_work_finished() const noexcept 698   24959 void on_work_finished() const noexcept
697   { 699   {
HITCBC 698   25220 ctx_->sched_->work_finished(); 700   24959 ctx_->sched_->work_finished();
HITCBC 699   25220 } 701   24959 }
700   702  
701   /** Dispatch a continuation. 703   /** Dispatch a continuation.
702   704  
703   Returns a handle for symmetric transfer. If called from 705   Returns a handle for symmetric transfer. If called from
704   within `run()`, returns `c.h`. Otherwise posts `c` for 706   within `run()`, returns `c.h`. Otherwise posts `c` for
705   later execution and returns `std::noop_coroutine()`. 707   later execution and returns `std::noop_coroutine()`.
706   708  
707   @param c The continuation to dispatch. 709   @param c The continuation to dispatch.
708   710  
709   @return A handle for symmetric transfer or `std::noop_coroutine()`. 711   @return A handle for symmetric transfer or `std::noop_coroutine()`.
710   712  
711   @pre The associated context must outlive this call. Dispatching 713   @pre The associated context must outlive this call. Dispatching
712   concurrently with, or after, the context's destruction is 714   concurrently with, or after, the context's destruction is
713   undefined behavior. 715   undefined behavior.
714   */ 716   */
HITCBC 715   493065 std::coroutine_handle<> dispatch(capy::continuation& c) const 717   505799 std::coroutine_handle<> dispatch(capy::continuation& c) const
716   { 718   {
HITCBC 717   493065 if (running_in_this_thread()) 719   505799 if (running_in_this_thread())
HITCBC 718   469234 return c.h; 720   482146 return c.h;
HITCBC 719   23831 post(c); 721   23653 post(c);
HITCBC 720   23831 return std::noop_coroutine(); 722   23653 return std::noop_coroutine();
721   } 723   }
722   724  
723   /** Post a continuation for deferred execution. 725   /** Post a continuation for deferred execution.
724   726  
725   Enqueues `c` directly on the scheduler's ready queue. 727   Enqueues `c` directly on the scheduler's ready queue.
726   No heap allocation occurs. 728   No heap allocation occurs.
727   729  
728   @param c The continuation to enqueue. 730   @param c The continuation to enqueue.
729   731  
730   @pre The associated context must outlive this call. Posting 732   @pre The associated context must outlive this call. Posting
731   concurrently with, or after, the context's destruction is 733   concurrently with, or after, the context's destruction is
732   undefined behavior. 734   undefined behavior.
733   */ 735   */
HITCBC 734   252134 void post(capy::continuation& c) const 736   252232 void post(capy::continuation& c) const
735   { 737   {
HITCBC 736   252134 ctx_->sched_->post(c); 738   252232 ctx_->sched_->post(c);
HITCBC 737   252134 } 739   252232 }
738   740  
739   /** Post a bare coroutine handle for deferred execution. 741   /** Post a bare coroutine handle for deferred execution.
740   742  
741   Heap-allocates a `scheduler_op` to wrap the handle. A caller 743   Heap-allocates a `scheduler_op` to wrap the handle. A caller
742   that already owns a `capy::continuation` can post it directly 744   that already owns a `capy::continuation` can post it directly
743   via the `post(capy::continuation&)` overload to avoid the 745   via the `post(capy::continuation&)` overload to avoid the
744   allocation. 746   allocation.
745   747  
746   A handle still queued when the context is torn down is 748   A handle still queued when the context is torn down is
747   destroyed alone, since it carries no owner to unwind its chain 749   destroyed alone, since it carries no owner to unwind its chain
748   from. Post a coroutine that another frame awaits through the 750   from. Post a coroutine that another frame awaits through the
749   `post(capy::continuation&)` overload, with its owner set. 751   `post(capy::continuation&)` overload, with its owner set.
750   752  
751   @param h The coroutine handle to post. 753   @param h The coroutine handle to post.
752   754  
753   @pre The associated context must outlive this call. Posting 755   @pre The associated context must outlive this call. Posting
754   concurrently with, or after, the context's destruction is 756   concurrently with, or after, the context's destruction is
755   undefined behavior. 757   undefined behavior.
756   758  
757   @pre Nothing awaits `h`. 759   @pre Nothing awaits `h`.
758   */ 760   */
HITCBC 759   3672 void post(std::coroutine_handle<> h) const 761   3672 void post(std::coroutine_handle<> h) const
760   { 762   {
HITCBC 761   3672 ctx_->sched_->post(h); 763   3672 ctx_->sched_->post(h);
HITCBC 762   3672 } 764   3672 }
763   765  
764   /** Compare two executors for equality. 766   /** Compare two executors for equality.
765   767  
766   @return `true` if both executors refer to the same context. 768   @return `true` if both executors refer to the same context.
767   */ 769   */
HITCBC 768   2 bool operator==(executor_type const& other) const noexcept 770   2 bool operator==(executor_type const& other) const noexcept
769   { 771   {
HITCBC 770   2 return ctx_ == other.ctx_; 772   2 return ctx_ == other.ctx_;
771   } 773   }
772   774  
773   /** Compare two executors for inequality. 775   /** Compare two executors for inequality.
774   776  
775   @return `true` if the executors refer to different contexts. 777   @return `true` if the executors refer to different contexts.
776   */ 778   */
777   bool operator!=(executor_type const& other) const noexcept 779   bool operator!=(executor_type const& other) const noexcept
778   { 780   {
779   return ctx_ != other.ctx_; 781   return ctx_ != other.ctx_;
780   } 782   }
781   }; 783   };
782   784  
783   inline io_context::executor_type 785   inline io_context::executor_type
HITCBC 784   18359 io_context::get_executor() const noexcept 786   18077 io_context::get_executor() const noexcept
785   { 787   {
HITCBC 786   18359 return executor_type(const_cast<io_context&>(*this)); 788   18077 return executor_type(const_cast<io_context&>(*this));
787   } 789   }
788   790  
789   } // namespace boost::corosio 791   } // namespace boost::corosio
790   792  
791   #endif // BOOST_COROSIO_IO_CONTEXT_HPP 793   #endif // BOOST_COROSIO_IO_CONTEXT_HPP