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 - /** Selects which internal locks the scheduler and reactor elide, 29 + /** Locking-safety tier for an @ref io_context.
30 - trading thread-safety guarantees for reduced synchronization  
31 - overhead.  
32   30  
33 - This is the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE` 31 + Selects which internal locks the scheduler and reactor elide, trading
34 - concurrency hint constants. The tier is chosen explicitly, not derived 32 + thread-safety guarantees for reduced synchronization overhead. This is
35 - from the `concurrency_hint`. (The reverse does apply: a lockless tier 33 + the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE` concurrency
36 - reduces the effective hint used for performance tuning to 1.) 34 + hint constants. The tier is chosen explicitly, not derived from the
  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`. A single thread must run 47 + Equivalent to Boost.Asio's `UNSAFE_IO`. The context must be run
48 - and drive the context. Resolver and POSIX file services remain 48 + and driven by a single thread, but resolver and POSIX file
49 - available, because they rely on scheduler locking, which stays 49 + services remain available (they rely on scheduler locking, which
50 - on. */ 50 + stays 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 - /** Configures scheduler and reactor tuning for an @ref io_context. 65 + /** Runtime tuning options for @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 them to 97 + and all three budget fields at their defaults overrides
98 - disable inline completion, giving post-everything mode. 98 + them to disable inline completion (post-everything mode),
99 - Multi-thread workloads benefit from cross-thread work-stealing. 99 + since multi-thread workloads benefit from cross-thread
100 - Setting any budget field to a non-default 100 + work-stealing. 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, which eliminates the `io_uring_enter` syscall on the submit 138 + store, eliminating 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. The next submit re-wakes it via SQ_WAKEUP. 0 151 + thread sleeps; 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 - /** Runs asynchronous operations and owns the I/O backend that drives them. 186 + /** An I/O context for running asynchronous operations.
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 - @pre The context must outlive every operation posted or dispatched 205 + @par Preconditions
206 - through its executor. No thread may be executing a run variant when 206 + The context must outlive every operation posted or dispatched
207 - the context is destroyed. Posting to the context 207 + through its executor, and no thread may be executing a run
208 - concurrently with, or after, its destruction is undefined 208 + variant when the context is destroyed. Posting to the context
209 - behavior. For a safe teardown, first stop submitting new work. 209 + concurrently with, or after, its destruction is undefined
210 - Then let every `run()` call return; each returns once no 210 + behavior. The safe teardown pattern is to stop submitting new
211 - outstanding work remains. Finally join the threads that ran the 211 + work, let every `run()` call return (each returns once no
212 - loop. Only then destroy the context. Work started with 212 + outstanding work remains), and join the threads that ran the
213 - `capy::run` / `capy::run_async` is work-tracked, so a normal 213 + loop before destroying the context. Work launched with
214 - `run()` completion already waits for it. 214 + `capy::run` / `capy::run_async` is work-tracked, so a normal
  215 + `run()` completion already waits for it.
215   216  
216   @par Exception Safety 217   @par Exception Safety
217 - A context that constructs is usable. The infrastructure its backend 218 + A context that constructs is usable. The infrastructure its
218 - needs — the completion port, the ring, the reactor's wakeup channel 219 + backend needs — the completion port, the ring, the reactor's
219 - — is created during construction. A system that refuses it therefore 220 + wakeup channel — is created during construction, so a system that
220 - throws from the constructor rather than from the first operation. 221 + refuses it throws from the constructor rather than from the first
221 - The failed construction leaves nothing open. 222 + operation, and the failed construction leaves nothing open.
222   223  
223   @par Thread Safety 224   @par Thread Safety
224   Distinct objects: Safe.@n 225   Distinct objects: Safe.@n
225   Shared objects: Safe, unless the context was constructed with a 226   Shared objects: Safe, unless the context was constructed with a
226   lockless @ref io_context_options::locking tier (`unsafe_io` or 227   lockless @ref io_context_options::locking tier (`unsafe_io` or
227   `unsafe`), in which case a single thread must drive it. 228   `unsafe`), in which case a single thread must drive it.
228   229  
229   @see epoll_t, select_t, kqueue_t, iocp_t 230   @see epoll_t, select_t, kqueue_t, iocp_t
230   */ 231   */
231   class BOOST_COROSIO_DECL io_context : public capy::execution_context 232   class BOOST_COROSIO_DECL io_context : public capy::execution_context
232   { 233   {
233   /// Reject invalid options before the backend is constructed. 234   /// Reject invalid options before the backend is constructed.
234   void apply_options_pre_(io_context_options const& opts); 235   void apply_options_pre_(io_context_options const& opts);
235   236  
236   /** Create the blocking-I/O thread pool, apply runtime tuning to the 237   /** Create the blocking-I/O thread pool, apply runtime tuning to the
237   scheduler and finish bringing the backend up. The tail of every 238   scheduler and finish bringing the backend up. The tail of every
238 - options constructor. The backend infrastructure whose setup reads 239 + options constructor: the backend infrastructure whose setup reads
239   these options is created here, so a failure to create it throws 240   these options is created here, so a failure to create it throws
240   from the constructor. */ 241   from the constructor. */
241   void apply_options_post_( 242   void apply_options_post_(
242   io_context_options const& opts, unsigned concurrency_hint); 243   io_context_options const& opts, unsigned concurrency_hint);
243   244  
244   /** Create the blocking-I/O thread pool and apply only the decomposed 245   /** Create the blocking-I/O thread pool and apply only the decomposed
245   threading configuration (locking tiers), then finish bringing the 246   threading configuration (locking tiers), then finish bringing the
246 - backend up. The tail of every plain constructor. Unlike the 247 + backend up. The tail of every plain constructor, which — unlike
247 - options constructors, it deliberately leaves the reactor budget 248 + the options constructors — deliberately leaves the reactor budget
248   at its defaults rather than engaging the multi-thread 249   at its defaults rather than engaging the multi-thread
249   post-everything heuristic. */ 250   post-everything heuristic. */
250   void apply_threading_(io_context_options const& opts); 251   void apply_threading_(io_context_options const& opts);
251   252  
252   protected: 253   protected:
253   detail::scheduler* sched_; 254   detail::scheduler* sched_;
254   255  
255   public: 256   public:
256 - /** Dispatches and posts work to this context; see the 257 + /** The executor type for this context. */
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 calls `run()`. 275 + that will call `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 calls `run()`. 287 + that will call `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 - @tparam Backend A backend tag type that provides a static  
301 - `construct(capy::execution_context&, unsigned)` factory  
302 - used to build the scheduler.  
303 -  
304   300  
305   @param backend The backend tag value selecting the I/O 301   @param backend The backend tag value selecting the I/O
306   multiplexer (e.g. `corosio::epoll`). 302   multiplexer (e.g. `corosio::epoll`).
307   @param concurrency_hint Hint for the number of threads 303   @param concurrency_hint Hint for the number of threads
308 - that calls `run()`. 304 + that will call `run()`.
309   305  
310   @throws std::system_error If the backend's infrastructure 306   @throws std::system_error If the backend's infrastructure
311   could not be created. 307   could not be created.
312   */ 308   */
313   template<class Backend> 309   template<class Backend>
314   requires requires { Backend::construct; } 310   requires requires { Backend::construct; }
HITCBC 315   1849 explicit io_context( 311   1849 explicit io_context(
316   [[maybe_unused]] Backend backend, 312   [[maybe_unused]] Backend backend,
317   unsigned concurrency_hint = std::thread::hardware_concurrency()) 313   unsigned concurrency_hint = std::thread::hardware_concurrency())
318   : capy::execution_context(this) 314   : capy::execution_context(this)
HITCBC 319   1849 , sched_(nullptr) 315   1849 , sched_(nullptr)
320   { 316   {
HITCBC 321   1849 sched_ = &Backend::construct(*this, concurrency_hint); 317   1849 sched_ = &Backend::construct(*this, concurrency_hint);
322   // Apply threading config only (locking tier). Unlike the options 318   // Apply threading config only (locking tier). Unlike the options
323   // ctor, the plain path leaves the reactor budget at its defaults. 319   // ctor, the plain path leaves the reactor budget at its defaults.
HITCBC 324   1837 apply_threading_(io_context_options{}); 320   1837 apply_threading_(io_context_options{});
HITCBC 325   1849 } 321   1849 }
326   322  
327   /** Construct with an explicit backend tag and runtime options. 323   /** Construct with an explicit backend tag and runtime options.
328 - @tparam Backend A backend tag type that provides a static  
329 - `construct(capy::execution_context&, unsigned)` factory  
330 - used to build the scheduler.  
331 -  
332   324  
333   @param backend The backend tag value selecting the I/O 325   @param backend The backend tag value selecting the I/O
334   multiplexer (e.g. `corosio::epoll`). 326   multiplexer (e.g. `corosio::epoll`).
335   @param opts Runtime options controlling scheduler and 327   @param opts Runtime options controlling scheduler and
336   service behavior. 328   service behavior.
337   @param concurrency_hint Hint for the number of threads 329   @param concurrency_hint Hint for the number of threads
338 - that calls `run()`. 330 + that will call `run()`.
339   331  
340   @throws std::invalid_argument If `opts.thread_pool_size` is 332   @throws std::invalid_argument If `opts.thread_pool_size` is
341   less than 1 (POSIX). 333   less than 1 (POSIX).
342   334  
343   @throws std::system_error If the backend's infrastructure 335   @throws std::system_error If the backend's infrastructure
344   could not be created. 336   could not be created.
345   */ 337   */
346   template<class Backend> 338   template<class Backend>
347   requires requires { Backend::construct; } 339   requires requires { Backend::construct; }
HITCBC 348   35 explicit io_context( 340   35 explicit io_context(
349   [[maybe_unused]] Backend backend, 341   [[maybe_unused]] Backend backend,
350   io_context_options const& opts, 342   io_context_options const& opts,
351   unsigned concurrency_hint = std::thread::hardware_concurrency()) 343   unsigned concurrency_hint = std::thread::hardware_concurrency())
352   : capy::execution_context(this) 344   : capy::execution_context(this)
HITCBC 353   35 , sched_(nullptr) 345   35 , sched_(nullptr)
354   { 346   {
HITCBC 355   35 apply_options_pre_(opts); 347   35 apply_options_pre_(opts);
356   // Effective hint (1 for lockless tiers); see effective_concurrency_hint. 348   // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
357   unsigned const eff = 349   unsigned const eff =
HITCBC 358   35 detail::effective_concurrency_hint(opts, concurrency_hint); 350   35 detail::effective_concurrency_hint(opts, concurrency_hint);
HITCBC 359   35 sched_ = &Backend::construct(*this, eff); 351   35 sched_ = &Backend::construct(*this, eff);
HITCBC 360   35 apply_options_post_(opts, eff); 352   35 apply_options_post_(opts, eff);
HITCBC 361   35 } 353   35 }
362 - /// Destroy the context; stops the loop and destroys every service.  
363   354  
364   ~io_context(); 355   ~io_context();
365   356  
366 - /// Copy construction is disabled; the context owns its services. 357 + io_context(io_context const&) = delete;
367 - io_context(io_context const&) = delete;  
368 - /// Copy assignment is disabled; the context owns its services.  
369   io_context& operator=(io_context const&) = delete; 358   io_context& operator=(io_context const&) = delete;
370   359  
371   /** Return an executor for this context. 360   /** Return an executor for this context.
372   361  
373   The returned executor can be used to dispatch coroutines 362   The returned executor can be used to dispatch coroutines
374   and post work items to this context. 363   and post work items to this context.
375   364  
376   @return An executor associated with this context. 365   @return An executor associated with this context.
377   */ 366   */
378   executor_type get_executor() const noexcept; 367   executor_type get_executor() const noexcept;
379   368  
380   /** Signal the context to stop processing. 369   /** Signal the context to stop processing.
381   370  
382   This causes `run()` to return as soon as possible. Any pending 371   This causes `run()` to return as soon as possible. Any pending
383   work items remain queued. 372   work items remain queued.
384   */ 373   */
HITCBC 385   13 void stop() 374   13 void stop()
386   { 375   {
HITCBC 387   13 sched_->stop(); 376   13 sched_->stop();
HITCBC 388   13 } 377   13 }
389   378  
390 - /** Return whether the context stopped. 379 + /** Return whether the context has been stopped.
391   380  
392 - @return `true` after a call to `stop()` with no later 381 + @return `true` if `stop()` has been called and `restart()`
393 - call to `restart()`. 382 + has not been called since.
394   */ 383   */
HITCBC 395   2482 bool stopped() const noexcept 384   2481 bool stopped() const noexcept
396   { 385   {
HITCBC 397   2482 return sched_->stopped(); 386   2481 return sched_->stopped();
398   } 387   }
399   388  
400   /** Restart the context after being stopped. 389   /** Restart the context after being stopped.
401   390  
402   This function must be called before `run()` can be called 391   This function must be called before `run()` can be called
403 - again after a call to `stop()`. 392 + again after `stop()` has been called.
404   */ 393   */
HITCBC 405   1423 void restart() 394   1423 void restart()
406   { 395   {
HITCBC 407   1423 sched_->restart(); 396   1423 sched_->restart();
HITCBC 408   1423 } 397   1423 }
409   398  
410   /** Process all pending work items. 399   /** Process all pending work items.
411   400  
412 - This function blocks until it executes all pending work items, 401 + This function blocks until all pending work items have been
413 - or until `stop()` is called. The context is stopped 402 + executed or `stop()` is called. The context is stopped
414   when there is no more outstanding work. 403   when there is no more outstanding work.
415   404  
416   @note The context must be restarted with `restart()` before 405   @note The context must be restarted with `restart()` before
417   calling this function again after it returns. 406   calling this function again after it returns.
418   407  
419   @return The number of handlers executed. 408   @return The number of handlers executed.
420   */ 409   */
HITCBC 421   1953 std::size_t run() 410   1979 std::size_t run()
422   { 411   {
HITCBC 423   1953 return sched_->run(); 412   1979 return sched_->run();
424   } 413   }
425   414  
426   /** Process at most one pending work item. 415   /** Process at most one pending work item.
427   416  
428 - This function blocks until it executes one work item 417 + This function blocks until one work item has been executed
429   or `stop()` is called. The context is stopped when there 418   or `stop()` is called. The context is stopped when there
430   is no more outstanding work. 419   is no more outstanding work.
431   420  
432   @note The context must be restarted with `restart()` before 421   @note The context must be restarted with `restart()` before
433   calling this function again after it returns. 422   calling this function again after it returns.
434   423  
435   @return The number of handlers executed (0 or 1). 424   @return The number of handlers executed (0 or 1).
436   */ 425   */
HITCBC 437   112 std::size_t run_one() 426   112 std::size_t run_one()
438   { 427   {
HITCBC 439   112 return sched_->run_one(); 428   112 return sched_->run_one();
440   } 429   }
441   430  
442   /** Process work items for the specified duration. 431   /** Process work items for the specified duration.
443   432  
444 - This function blocks until it has executed work items for the 433 + This function blocks until work items have been executed for
445 - specified duration, or until `stop()` is called. The context 434 + the specified duration, or `stop()` is called. The context
446   is stopped when there is no more outstanding work. 435   is stopped when there is no more outstanding work.
447   436  
448   @note The context must be restarted with `restart()` before 437   @note The context must be restarted with `restart()` before
449   calling this function again after it returns. 438   calling this function again after it returns.
450   439  
451   @param rel_time The duration for which to process work. 440   @param rel_time The duration for which to process work.
452   441  
453   @return The number of handlers executed. 442   @return The number of handlers executed.
454   */ 443   */
455   template<class Rep, class Period> 444   template<class Rep, class Period>
HITCBC 456   815 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time) 445   815 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
457   { 446   {
HITCBC 458   815 return run_until(std::chrono::steady_clock::now() + rel_time); 447   815 return run_until(std::chrono::steady_clock::now() + rel_time);
459   } 448   }
460   449  
461   /** Process work items until the specified time. 450   /** Process work items until the specified time.
462   451  
463   This function blocks until the specified time is reached 452   This function blocks until the specified time is reached
464   or `stop()` is called. The context is stopped when there 453   or `stop()` is called. The context is stopped when there
465   is no more outstanding work. 454   is no more outstanding work.
466   455  
467   @note The context must be restarted with `restart()` before 456   @note The context must be restarted with `restart()` before
468   calling this function again after it returns. 457   calling this function again after it returns.
469   458  
470   @param abs_time The time point until which to process work. 459   @param abs_time The time point until which to process work.
471   460  
472   @return The number of handlers executed. 461   @return The number of handlers executed.
473   */ 462   */
474   template<class Clock, class Duration> 463   template<class Clock, class Duration>
475   std::size_t 464   std::size_t
HITCBC 476   816 run_until(std::chrono::time_point<Clock, Duration> const& abs_time) 465   816 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
477   { 466   {
HITCBC 478   816 std::size_t n = 0; 467   816 std::size_t n = 0;
HITCBC 479   2407 while (run_one_until(abs_time)) 468   2408 while (run_one_until(abs_time))
HITCBC 480   1591 if (n != (std::numeric_limits<std::size_t>::max)()) 469   1592 if (n != (std::numeric_limits<std::size_t>::max)())
HITCBC 481   1591 ++n; 470   1592 ++n;
HITCBC 482   816 return n; 471   816 return n;
483   } 472   }
484   473  
485   /** Process at most one work item for the specified duration. 474   /** Process at most one work item for the specified duration.
486   475  
487 - This function blocks until it executes one work item, 476 + This function blocks until one work item has been executed,
488   the specified duration has elapsed, or `stop()` is called. 477   the specified duration has elapsed, or `stop()` is called.
489   The context is stopped when there is no more outstanding work. 478   The context is stopped when there is no more outstanding work.
490   479  
491   @note The context must be restarted with `restart()` before 480   @note The context must be restarted with `restart()` before
492   calling this function again after it returns. 481   calling this function again after it returns.
493   482  
494   @param rel_time The duration for which the call may block. 483   @param rel_time The duration for which the call may block.
495   484  
496   @return The number of handlers executed (0 or 1). 485   @return The number of handlers executed (0 or 1).
497   */ 486   */
498   template<class Rep, class Period> 487   template<class Rep, class Period>
HITCBC 499   75 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time) 488   75 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
500   { 489   {
HITCBC 501   75 return run_one_until(std::chrono::steady_clock::now() + rel_time); 490   75 return run_one_until(std::chrono::steady_clock::now() + rel_time);
502   } 491   }
503   492  
504   /** Process at most one work item until the specified time. 493   /** Process at most one work item until the specified time.
505   494  
506 - This function blocks until it executes one work item, 495 + This function blocks until one work item has been executed,
507   the specified time is reached, or `stop()` is called. 496   the specified time is reached, or `stop()` is called.
508   The context is stopped when there is no more outstanding work. 497   The context is stopped when there is no more outstanding work.
509   498  
510   @note The context must be restarted with `restart()` before 499   @note The context must be restarted with `restart()` before
511   calling this function again after it returns. 500   calling this function again after it returns.
512   501  
513   @param abs_time The time point until which the call may block. 502   @param abs_time The time point until which the call may block.
514   503  
515   @return The number of handlers executed (0 or 1). 504   @return The number of handlers executed (0 or 1).
516   */ 505   */
517   template<class Clock, class Duration> 506   template<class Clock, class Duration>
518   std::size_t 507   std::size_t
HITCBC 519   2490 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time) 508   2491 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
520   { 509   {
HITCBC 521   2490 typename Clock::time_point now = Clock::now(); 510   2491 typename Clock::time_point now = Clock::now();
HITCBC 522   1592 for (;;) 511   1591 for (;;)
523   { 512   {
HITCBC 524   4082 auto rel_time = abs_time - now; 513   4082 auto rel_time = abs_time - now;
525   using rel_type = decltype(rel_time); 514   using rel_type = decltype(rel_time);
HITCBC 526   4082 if (rel_time < rel_type::zero()) 515   4082 if (rel_time < rel_type::zero())
HITCBC 527   5 rel_time = rel_type::zero(); 516   5 rel_time = rel_type::zero();
HITCBC 528   4077 else if (rel_time > std::chrono::seconds(1)) 517   4077 else if (rel_time > std::chrono::seconds(1))
HITCBC 529   3966 rel_time = std::chrono::seconds(1); 518   3968 rel_time = std::chrono::seconds(1);
530   519  
HITCBC 531   4082 std::size_t s = sched_->wait_one( 520   4082 std::size_t s = sched_->wait_one(
532   static_cast<long>( 521   static_cast<long>(
HITCBC 533   4082 std::chrono::duration_cast<std::chrono::microseconds>( 522   4082 std::chrono::duration_cast<std::chrono::microseconds>(
534   rel_time) 523   rel_time)
HITCBC 535   4082 .count())); 524   4082 .count()));
536   525  
HITCBC 537   4082 if (s || stopped()) 526   4082 if (s || stopped())
HITCBC 538   2490 return s; 527   2491 return s;
539   528  
HITCBC 540   1618 now = Clock::now(); 529   1617 now = Clock::now();
HITCBC 541   1618 if (now >= abs_time) 530   1617 if (now >= abs_time)
HITCBC 542   26 return 0; 531   26 return 0;
543   } 532   }
544   } 533   }
545   534  
546   /** Process all ready work items without blocking. 535   /** Process all ready work items without blocking.
547   536  
548   This function executes all work items that are ready to run 537   This function executes all work items that are ready to run
549   without blocking for more work. The context is stopped 538   without blocking for more work. The context is stopped
550   when there is no more outstanding work. 539   when there is no more outstanding work.
551   540  
552   @note The context must be restarted with `restart()` before 541   @note The context must be restarted with `restart()` before
553   calling this function again after it returns. 542   calling this function again after it returns.
554   543  
555   @return The number of handlers executed. 544   @return The number of handlers executed.
556   */ 545   */
HITCBC 557   47 std::size_t poll() 546   47 std::size_t poll()
558   { 547   {
HITCBC 559   47 return sched_->poll(); 548   47 return sched_->poll();
560   } 549   }
561   550  
562   /** Process at most one ready work item without blocking. 551   /** Process at most one ready work item without blocking.
563   552  
564   This function executes at most one work item that is ready 553   This function executes at most one work item that is ready
565   to run without blocking for more work. The context is 554   to run without blocking for more work. The context is
566   stopped when there is no more outstanding work. 555   stopped when there is no more outstanding work.
567   556  
568   @note The context must be restarted with `restart()` before 557   @note The context must be restarted with `restart()` before
569   calling this function again after it returns. 558   calling this function again after it returns.
570   559  
571   @return The number of handlers executed (0 or 1). 560   @return The number of handlers executed (0 or 1).
572   */ 561   */
HITCBC 573   11 std::size_t poll_one() 562   11 std::size_t poll_one()
574   { 563   {
HITCBC 575   11 return sched_->poll_one(); 564   11 return sched_->poll_one();
576   } 565   }
577   }; 566   };
578   567  
579 - /** Dispatches and posts work to an I/O context. 568 + /** An executor for dispatching work to an I/O context.
580   569  
581   The executor provides the interface for posting work items and 570   The executor provides the interface for posting work items and
582   dispatching coroutines to the associated context. It satisfies 571   dispatching coroutines to the associated context. It satisfies
583   the `capy::Executor` concept. 572   the `capy::Executor` concept.
584   573  
585   Executors are lightweight handles that can be copied and compared 574   Executors are lightweight handles that can be copied and compared
586   for equality. Two executors compare equal if they refer to the 575   for equality. Two executors compare equal if they refer to the
587   same context. 576   same context.
588   577  
589   @par Thread Safety 578   @par Thread Safety
590   Distinct objects: Safe.@n 579   Distinct objects: Safe.@n
591   Shared objects: Safe. 580   Shared objects: Safe.
592   */ 581   */
593   class io_context::executor_type 582   class io_context::executor_type
594   { 583   {
595   io_context* ctx_ = nullptr; 584   io_context* ctx_ = nullptr;
596   585  
597   public: 586   public:
598 - /** Constructs an executor not associated with any context. */ 587 + /** Default constructor.
  588 +
  589 + Constructs an executor not associated with any context.
  590 + */
HITCBC 599   2053 executor_type() = default; 591   2053 executor_type() = default;
600   592  
601   /** Construct an executor from a context. 593   /** Construct an executor from a context.
602   594  
603   @param ctx The context to associate with this executor. 595   @param ctx The context to associate with this executor.
604   */ 596   */
HITCBC 605   5262 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {} 597   5262 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
606   598  
607   /** Return a reference to the associated execution context. 599   /** Return a reference to the associated execution context.
608   600  
609   @return Reference to the context. 601   @return Reference to the context.
610   */ 602   */
HITCBC 611   27382 io_context& context() const noexcept 603   27451 io_context& context() const noexcept
612   { 604   {
HITCBC 613   27382 return *ctx_; 605   27451 return *ctx_;
614   } 606   }
615   607  
616   /** Check if the current thread is running this executor's context. 608   /** Check if the current thread is running this executor's context.
617   609  
618   @return `true` if `run()` is being called on this thread. 610   @return `true` if `run()` is being called on this thread.
619   */ 611   */
HITCBC 620   10766 bool running_in_this_thread() const noexcept 612   10789 bool running_in_this_thread() const noexcept
621   { 613   {
HITCBC 622   10766 return ctx_->sched_->running_in_this_thread(); 614   10789 return ctx_->sched_->running_in_this_thread();
623   } 615   }
624   616  
625   /** Informs the executor that work is beginning. 617   /** Informs the executor that work is beginning.
626   618  
627   Must be paired with `on_work_finished()`. 619   Must be paired with `on_work_finished()`.
628   */ 620   */
HITCBC 629   11173 void on_work_started() const noexcept 621   11222 void on_work_started() const noexcept
630   { 622   {
HITCBC 631   11173 ctx_->sched_->work_started(); 623   11222 ctx_->sched_->work_started();
HITCBC 632   11173 } 624   11222 }
633   625  
634   /** Informs the executor that work has completed. 626   /** Informs the executor that work has completed.
635   627  
636 - @pre A preceding call to `on_work_started()` on an equal executor. 628 + @par Preconditions
  629 + A preceding call to `on_work_started()` on an equal executor.
637   */ 630   */
HITCBC 638   11111 void on_work_finished() const noexcept 631   11160 void on_work_finished() const noexcept
639   { 632   {
HITCBC 640   11111 ctx_->sched_->work_finished(); 633   11160 ctx_->sched_->work_finished();
HITCBC 641   11111 } 634   11160 }
642   635  
643   /** Dispatch a continuation. 636   /** Dispatch a continuation.
644   637  
645   Returns a handle for symmetric transfer. If called from 638   Returns a handle for symmetric transfer. If called from
646   within `run()`, returns `c.h`. Otherwise posts `c` for 639   within `run()`, returns `c.h`. Otherwise posts `c` for
647   later execution and returns `std::noop_coroutine()`. 640   later execution and returns `std::noop_coroutine()`.
648   641  
649   @param c The continuation to dispatch. 642   @param c The continuation to dispatch.
650   643  
651   @return A handle for symmetric transfer or `std::noop_coroutine()`. 644   @return A handle for symmetric transfer or `std::noop_coroutine()`.
652   645  
653 - @pre The associated context must outlive this call. Dispatching 646 + @par Preconditions
654 - concurrently with, or after, the context's destruction is 647 + The associated context must outlive this call. Dispatching
655 - undefined behavior. 648 + concurrently with, or after, the context's destruction is
  649 + undefined behavior.
656   */ 650   */
HITCBC 657   10761 std::coroutine_handle<> dispatch(capy::continuation& c) const 651   10784 std::coroutine_handle<> dispatch(capy::continuation& c) const
658   { 652   {
HITCBC 659   10761 if (running_in_this_thread()) 653   10784 if (running_in_this_thread())
HITCBC 660   942 return c.h; 654   939 return c.h;
HITCBC 661   9819 post(c); 655   9845 post(c);
HITCBC 662   9819 return std::noop_coroutine(); 656   9845 return std::noop_coroutine();
663   } 657   }
664   658  
665   /** Post a continuation for deferred execution. 659   /** Post a continuation for deferred execution.
666   660  
667   Enqueues `c` directly on the scheduler's ready queue. 661   Enqueues `c` directly on the scheduler's ready queue.
668   No heap allocation occurs. 662   No heap allocation occurs.
669   663  
670 - @param c The continuation to enqueue. 664 + @par Preconditions
671 - 665 + The associated context must outlive this call. Posting
672 - @pre The associated context must outlive this call. Posting 666 + concurrently with, or after, the context's destruction is
673 - concurrently with, or after, the context's destruction is 667 + undefined behavior.
674 - undefined behavior.  
675   */ 668   */
HITCBC 676   25835 void post(capy::continuation& c) const 669   25908 void post(capy::continuation& c) const
677   { 670   {
HITCBC 678   25835 ctx_->sched_->post(c); 671   25908 ctx_->sched_->post(c);
HITCBC 679   25835 } 672   25908 }
680   673  
681   /** Post a bare coroutine handle for deferred execution. 674   /** Post a bare coroutine handle for deferred execution.
682   675  
683 - Heap-allocates a `scheduler_op` to wrap the handle. A caller 676 + Heap-allocates a scheduler_op to wrap the handle. A caller
684 - that already owns a `capy::continuation` can post it directly 677 + that already owns a `scheduler_op` can post it directly via
685 - via the `post(capy::continuation&)` overload to avoid the 678 + the `post(scheduler_op*)` overload to avoid the allocation.
686 - allocation.  
687   679  
688   @param h The coroutine handle to post. 680   @param h The coroutine handle to post.
689   681  
690 - @pre The associated context must outlive this call. Posting 682 + @par Preconditions
691 - concurrently with, or after, the context's destruction is 683 + The associated context must outlive this call. Posting
692 - undefined behavior. 684 + concurrently with, or after, the context's destruction is
  685 + undefined behavior.
693   */ 686   */
HITCBC 694   3756 void post(std::coroutine_handle<> h) const 687   3756 void post(std::coroutine_handle<> h) const
695   { 688   {
HITCBC 696   3756 ctx_->sched_->post(h); 689   3756 ctx_->sched_->post(h);
HITCBC 697   3756 } 690   3756 }
698   691  
699   /** Compare two executors for equality. 692   /** Compare two executors for equality.
700   693  
701   @return `true` if both executors refer to the same context. 694   @return `true` if both executors refer to the same context.
702   */ 695   */
HITCBC 703   2 bool operator==(executor_type const& other) const noexcept 696   2 bool operator==(executor_type const& other) const noexcept
704   { 697   {
HITCBC 705   2 return ctx_ == other.ctx_; 698   2 return ctx_ == other.ctx_;
706   } 699   }
707   700  
708   /** Compare two executors for inequality. 701   /** Compare two executors for inequality.
709   702  
710   @return `true` if the executors refer to different contexts. 703   @return `true` if the executors refer to different contexts.
711   */ 704   */
712   bool operator!=(executor_type const& other) const noexcept 705   bool operator!=(executor_type const& other) const noexcept
713   { 706   {
714   return ctx_ != other.ctx_; 707   return ctx_ != other.ctx_;
715   } 708   }
716   }; 709   };
717   710  
718   inline io_context::executor_type 711   inline io_context::executor_type
HITCBC 719   5262 io_context::get_executor() const noexcept 712   5262 io_context::get_executor() const noexcept
720   { 713   {
HITCBC 721   5262 return executor_type(const_cast<io_context&>(*this)); 714   5262 return executor_type(const_cast<io_context&>(*this));
722   } 715   }
723   716  
724   } // namespace boost::corosio 717   } // namespace boost::corosio
725   718  
726   #endif // BOOST_COROSIO_IO_CONTEXT_HPP 719   #endif // BOOST_COROSIO_IO_CONTEXT_HPP