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