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