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