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