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, unsigned concurrency_hint); 243 + io_context_options const& opts,
  244 + unsigned concurrency_hint);
244   245  
245   /** Create the blocking-I/O thread pool and apply only the decomposed 246   /** Create the blocking-I/O thread pool and apply only the decomposed
246   threading configuration (locking tiers), then finish bringing the 247   threading configuration (locking tiers), then finish bringing the
247   backend up. The tail of every plain constructor, which — unlike 248   backend up. The tail of every plain constructor, which — unlike
248   the options constructors — deliberately leaves the reactor budget 249   the options constructors — deliberately leaves the reactor budget
249   at its defaults rather than engaging the multi-thread 250   at its defaults rather than engaging the multi-thread
250   post-everything heuristic. */ 251   post-everything heuristic. */
251   void apply_threading_(io_context_options const& opts); 252   void apply_threading_(io_context_options const& opts);
252   253  
253   protected: 254   protected:
254   detail::scheduler* sched_; 255   detail::scheduler* sched_;
255   256  
256   public: 257   public:
257   /** The executor type for this context. */ 258   /** The executor type for this context. */
258   class executor_type; 259   class executor_type;
259   260  
260   /** Construct with default concurrency and platform backend. 261   /** Construct with default concurrency and platform backend.
261   262  
262   Uses `std::thread::hardware_concurrency()` (floored to 1, in 263   Uses `std::thread::hardware_concurrency()` (floored to 1, in
263   case it reports 0) as the concurrency hint, and the default 264   case it reports 0) as the concurrency hint, and the default
264   @ref locking_mode::safe tier. Select a lockless tier via 265   @ref locking_mode::safe tier. Select a lockless tier via
265   @ref io_context_options::locking. 266   @ref io_context_options::locking.
266   267  
267   @throws std::system_error If the backend's infrastructure 268   @throws std::system_error If the backend's infrastructure
268   could not be created. 269   could not be created.
269   */ 270   */
270   io_context(); 271   io_context();
271   272  
272   /** Construct with a concurrency hint and platform backend. 273   /** Construct with a concurrency hint and platform backend.
273   274  
274   @param concurrency_hint Hint for the number of threads 275   @param concurrency_hint Hint for the number of threads
275   that will call `run()`. 276   that will call `run()`.
276   277  
277   @throws std::system_error If the backend's infrastructure 278   @throws std::system_error If the backend's infrastructure
278   could not be created. 279   could not be created.
279   */ 280   */
280   explicit io_context(unsigned concurrency_hint); 281   explicit io_context(unsigned concurrency_hint);
281   282  
282   /** Construct with runtime tuning options and platform backend. 283   /** Construct with runtime tuning options and platform backend.
283   284  
284   @param opts Runtime options controlling scheduler and 285   @param opts Runtime options controlling scheduler and
285   service behavior. 286   service behavior.
286   @param concurrency_hint Hint for the number of threads 287   @param concurrency_hint Hint for the number of threads
287   that will call `run()`. 288   that will call `run()`.
288   289  
289   @throws std::invalid_argument If `opts.thread_pool_size` is 290   @throws std::invalid_argument If `opts.thread_pool_size` is
290   less than 1 (POSIX). 291   less than 1 (POSIX).
291   292  
292   @throws std::system_error If the backend's infrastructure 293   @throws std::system_error If the backend's infrastructure
293   could not be created. 294   could not be created.
294   */ 295   */
295   explicit io_context( 296   explicit io_context(
296   io_context_options const& opts, 297   io_context_options const& opts,
297   unsigned concurrency_hint = std::thread::hardware_concurrency()); 298   unsigned concurrency_hint = std::thread::hardware_concurrency());
298   299  
299   /** Construct with an explicit backend tag. 300   /** Construct with an explicit backend tag.
300   301  
301   @param backend The backend tag value selecting the I/O 302   @param backend The backend tag value selecting the I/O
302   multiplexer (e.g. `corosio::epoll`). 303   multiplexer (e.g. `corosio::epoll`).
303   @param concurrency_hint Hint for the number of threads 304   @param concurrency_hint Hint for the number of threads
304   that will call `run()`. 305   that will call `run()`.
305   306  
306   @throws std::system_error If the backend's infrastructure 307   @throws std::system_error If the backend's infrastructure
307   could not be created. 308   could not be created.
308   */ 309   */
309   template<class Backend> 310   template<class Backend>
310   requires requires { Backend::construct; } 311   requires requires { Backend::construct; }
HITCBC 311   1729 explicit io_context( 312   1729 explicit io_context(
312   [[maybe_unused]] Backend backend, 313   [[maybe_unused]] Backend backend,
313   unsigned concurrency_hint = std::thread::hardware_concurrency()) 314   unsigned concurrency_hint = std::thread::hardware_concurrency())
314   : capy::execution_context(this) 315   : capy::execution_context(this)
HITCBC 315   1729 , sched_(nullptr) 316   1729 , sched_(nullptr)
316   { 317   {
HITCBC 317   1729 sched_ = &Backend::construct(*this, concurrency_hint); 318   1729 sched_ = &Backend::construct(*this, concurrency_hint);
318   // Apply threading config only (locking tier). Unlike the options 319   // Apply threading config only (locking tier). Unlike the options
319   // ctor, the plain path leaves the reactor budget at its defaults. 320   // ctor, the plain path leaves the reactor budget at its defaults.
HITCBC 320   1717 apply_threading_(io_context_options{}); 321   1717 apply_threading_(io_context_options{});
HITCBC 321   1729 } 322   1729 }
322   323  
323   /** Construct with an explicit backend tag and runtime options. 324   /** Construct with an explicit backend tag and runtime options.
324   325  
325   @param backend The backend tag value selecting the I/O 326   @param backend The backend tag value selecting the I/O
326   multiplexer (e.g. `corosio::epoll`). 327   multiplexer (e.g. `corosio::epoll`).
327   @param opts Runtime options controlling scheduler and 328   @param opts Runtime options controlling scheduler and
328   service behavior. 329   service behavior.
329   @param concurrency_hint Hint for the number of threads 330   @param concurrency_hint Hint for the number of threads
330   that will call `run()`. 331   that will call `run()`.
331   332  
332   @throws std::invalid_argument If `opts.thread_pool_size` is 333   @throws std::invalid_argument If `opts.thread_pool_size` is
333   less than 1 (POSIX). 334   less than 1 (POSIX).
334   335  
335   @throws std::system_error If the backend's infrastructure 336   @throws std::system_error If the backend's infrastructure
336   could not be created. 337   could not be created.
337   */ 338   */
338   template<class Backend> 339   template<class Backend>
339   requires requires { Backend::construct; } 340   requires requires { Backend::construct; }
HITCBC 340   27 explicit io_context( 341   27 explicit io_context(
341   [[maybe_unused]] Backend backend, 342   [[maybe_unused]] Backend backend,
342   io_context_options const& opts, 343   io_context_options const& opts,
343   unsigned concurrency_hint = std::thread::hardware_concurrency()) 344   unsigned concurrency_hint = std::thread::hardware_concurrency())
344   : capy::execution_context(this) 345   : capy::execution_context(this)
HITCBC 345   27 , sched_(nullptr) 346   27 , sched_(nullptr)
346   { 347   {
HITCBC 347   27 apply_options_pre_(opts); 348   27 apply_options_pre_(opts);
348   // Effective hint (1 for lockless tiers); see effective_concurrency_hint. 349   // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
349   unsigned const eff = 350   unsigned const eff =
HITCBC 350   27 detail::effective_concurrency_hint(opts, concurrency_hint); 351   27 detail::effective_concurrency_hint(opts, concurrency_hint);
HITCBC 351   27 sched_ = &Backend::construct(*this, eff); 352   27 sched_ = &Backend::construct(*this, eff);
HITCBC 352   27 apply_options_post_(opts, eff); 353   27 apply_options_post_(opts, eff);
HITCBC 353   27 } 354   27 }
354   355  
355   ~io_context(); 356   ~io_context();
356   357  
357   io_context(io_context const&) = delete; 358   io_context(io_context const&) = delete;
358   io_context& operator=(io_context const&) = delete; 359   io_context& operator=(io_context const&) = delete;
359   360  
360   /** Return an executor for this context. 361   /** Return an executor for this context.
361   362  
362   The returned executor can be used to dispatch coroutines 363   The returned executor can be used to dispatch coroutines
363   and post work items to this context. 364   and post work items to this context.
364   365  
365   @return An executor associated with this context. 366   @return An executor associated with this context.
366   */ 367   */
367   executor_type get_executor() const noexcept; 368   executor_type get_executor() const noexcept;
368   369  
369   /** Signal the context to stop processing. 370   /** Signal the context to stop processing.
370   371  
371   This causes `run()` to return as soon as possible. Any pending 372   This causes `run()` to return as soon as possible. Any pending
372   work items remain queued. 373   work items remain queued.
373   */ 374   */
HITCBC 374   13 void stop() 375   13 void stop()
375   { 376   {
HITCBC 376   13 sched_->stop(); 377   13 sched_->stop();
HITCBC 377   13 } 378   13 }
378   379  
379   /** Return whether the context has been stopped. 380   /** Return whether the context has been stopped.
380   381  
381   @return `true` if `stop()` has been called and `restart()` 382   @return `true` if `stop()` has been called and `restart()`
382   has not been called since. 383   has not been called since.
383   */ 384   */
HITCBC 384   116 bool stopped() const noexcept 385   119 bool stopped() const noexcept
385   { 386   {
HITCBC 386   116 return sched_->stopped(); 387   119 return sched_->stopped();
387   } 388   }
388   389  
389   /** Restart the context after being stopped. 390   /** Restart the context after being stopped.
390   391  
391   This function must be called before `run()` can be called 392   This function must be called before `run()` can be called
392   again after `stop()` has been called. 393   again after `stop()` has been called.
393   */ 394   */
HITCBC 394   447 void restart() 395   447 void restart()
395   { 396   {
HITCBC 396   447 sched_->restart(); 397   447 sched_->restart();
HITCBC 397   447 } 398   447 }
398   399  
399   /** Process all pending work items. 400   /** Process all pending work items.
400   401  
401   This function blocks until all pending work items have been 402   This function blocks until all pending work items have been
402   executed or `stop()` is called. The context is stopped 403   executed or `stop()` is called. The context is stopped
403   when there is no more outstanding work. 404   when there is no more outstanding work.
404   405  
405   @note The context must be restarted with `restart()` before 406   @note The context must be restarted with `restart()` before
406   calling this function again after it returns. 407   calling this function again after it returns.
407   408  
408   @return The number of handlers executed. 409   @return The number of handlers executed.
409   */ 410   */
HITCBC 410   1681 std::size_t run() 411   1681 std::size_t run()
411   { 412   {
HITCBC 412   1681 return sched_->run(); 413   1681 return sched_->run();
413   } 414   }
414   415  
415   /** Process at most one pending work item. 416   /** Process at most one pending work item.
416   417  
417   This function blocks until one work item has been executed 418   This function blocks until one work item has been executed
418   or `stop()` is called. The context is stopped when there 419   or `stop()` is called. The context is stopped when there
419   is no more outstanding work. 420   is no more outstanding work.
420   421  
421   @note The context must be restarted with `restart()` before 422   @note The context must be restarted with `restart()` before
422   calling this function again after it returns. 423   calling this function again after it returns.
423   424  
424   @return The number of handlers executed (0 or 1). 425   @return The number of handlers executed (0 or 1).
425   */ 426   */
HITCBC 426   112 std::size_t run_one() 427   112 std::size_t run_one()
427   { 428   {
HITCBC 428   112 return sched_->run_one(); 429   112 return sched_->run_one();
429   } 430   }
430   431  
431   /** Process work items for the specified duration. 432   /** Process work items for the specified duration.
432   433  
433   This function blocks until work items have been executed for 434   This function blocks until work items have been executed for
434   the specified duration, or `stop()` is called. The context 435   the specified duration, or `stop()` is called. The context
435   is stopped when there is no more outstanding work. 436   is stopped when there is no more outstanding work.
436   437  
437   @note The context must be restarted with `restart()` before 438   @note The context must be restarted with `restart()` before
438   calling this function again after it returns. 439   calling this function again after it returns.
439   440  
440   @param rel_time The duration for which to process work. 441   @param rel_time The duration for which to process work.
441   442  
442   @return The number of handlers executed. 443   @return The number of handlers executed.
443   */ 444   */
444   template<class Rep, class Period> 445   template<class Rep, class Period>
HITCBC 445   15 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time) 446   15 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
446   { 447   {
HITCBC 447   15 return run_until(std::chrono::steady_clock::now() + rel_time); 448   15 return run_until(std::chrono::steady_clock::now() + rel_time);
448   } 449   }
449   450  
450   /** Process work items until the specified time. 451   /** Process work items until the specified time.
451   452  
452   This function blocks until the specified time is reached 453   This function blocks until the specified time is reached
453   or `stop()` is called. The context is stopped when there 454   or `stop()` is called. The context is stopped when there
454   is no more outstanding work. 455   is no more outstanding work.
455   456  
456   @note The context must be restarted with `restart()` before 457   @note The context must be restarted with `restart()` before
457   calling this function again after it returns. 458   calling this function again after it returns.
458   459  
459   @param abs_time The time point until which to process work. 460   @param abs_time The time point until which to process work.
460   461  
461   @return The number of handlers executed. 462   @return The number of handlers executed.
462   */ 463   */
463   template<class Clock, class Duration> 464   template<class Clock, class Duration>
464   std::size_t 465   std::size_t
HITCBC 465   16 run_until(std::chrono::time_point<Clock, Duration> const& abs_time) 466   16 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
466   { 467   {
HITCBC 467   16 std::size_t n = 0; 468   16 std::size_t n = 0;
HITCBC 468   43 while (run_one_until(abs_time)) 469   43 while (run_one_until(abs_time))
HITCBC 469   27 if (n != (std::numeric_limits<std::size_t>::max)()) 470   27 if (n != (std::numeric_limits<std::size_t>::max)())
HITCBC 470   27 ++n; 471   27 ++n;
HITCBC 471   16 return n; 472   16 return n;
472   } 473   }
473   474  
474   /** Process at most one work item for the specified duration. 475   /** Process at most one work item for the specified duration.
475   476  
476   This function blocks until one work item has been executed, 477   This function blocks until one work item has been executed,
477   the specified duration has elapsed, or `stop()` is called. 478   the specified duration has elapsed, or `stop()` is called.
478   The context is stopped when there is no more outstanding work. 479   The context is stopped when there is no more outstanding work.
479   480  
480   @note The context must be restarted with `restart()` before 481   @note The context must be restarted with `restart()` before
481   calling this function again after it returns. 482   calling this function again after it returns.
482   483  
483   @param rel_time The duration for which the call may block. 484   @param rel_time The duration for which the call may block.
484   485  
485   @return The number of handlers executed (0 or 1). 486   @return The number of handlers executed (0 or 1).
486   */ 487   */
487   template<class Rep, class Period> 488   template<class Rep, class Period>
HITCBC 488   75 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time) 489   74 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
489   { 490   {
HITCBC 490   75 return run_one_until(std::chrono::steady_clock::now() + rel_time); 491   74 return run_one_until(std::chrono::steady_clock::now() + rel_time);
491   } 492   }
492   493  
493   /** Process at most one work item until the specified time. 494   /** Process at most one work item until the specified time.
494   495  
495   This function blocks until one work item has been executed, 496   This function blocks until one work item has been executed,
496   the specified time is reached, or `stop()` is called. 497   the specified time is reached, or `stop()` is called.
497   The context is stopped when there is no more outstanding work. 498   The context is stopped when there is no more outstanding work.
498   499  
499   @note The context must be restarted with `restart()` before 500   @note The context must be restarted with `restart()` before
500   calling this function again after it returns. 501   calling this function again after it returns.
501   502  
502   @param abs_time The time point until which the call may block. 503   @param abs_time The time point until which the call may block.
503   504  
504   @return The number of handlers executed (0 or 1). 505   @return The number of handlers executed (0 or 1).
505   */ 506   */
506   template<class Clock, class Duration> 507   template<class Clock, class Duration>
507   std::size_t 508   std::size_t
HITCBC 508   126 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time) 509   125 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
509   { 510   {
HITCBC 510   126 typename Clock::time_point now = Clock::now(); 511   125 typename Clock::time_point now = Clock::now();
HITCBC 511   26 for (;;) 512   30 for (;;)
512   { 513   {
HITCBC 513 - 152 auto rel_time = abs_time - now; 514 + 155 auto rel_time = abs_time - now;
514   using rel_type = decltype(rel_time); 515   using rel_type = decltype(rel_time);
HITCBC 515   152 if (rel_time < rel_type::zero()) 516   155 if (rel_time < rel_type::zero())
HITCBC 516   5 rel_time = rel_type::zero(); 517   5 rel_time = rel_type::zero();
HITCBC 517   147 else if (rel_time > std::chrono::seconds(1)) 518   150 else if (rel_time > std::chrono::seconds(1))
HITCBC 518   38 rel_time = std::chrono::seconds(1); 519   38 rel_time = std::chrono::seconds(1);
519   520  
HITCBC 520   152 std::size_t s = sched_->wait_one( 521   155 std::size_t s = sched_->wait_one(
521   static_cast<long>( 522   static_cast<long>(
HITCBC 522   152 std::chrono::duration_cast<std::chrono::microseconds>( 523   155 std::chrono::duration_cast<std::chrono::microseconds>(
523   rel_time) 524   rel_time)
HITCBC 524   152 .count())); 525   155 .count()));
525   526  
HITCBC 526   152 if (s || stopped()) 527   155 if (s || stopped())
HITCBC 527   126 return s; 528   125 return s;
528   529  
HITCBC 529   52 now = Clock::now(); 530   55 now = Clock::now();
HITCBC 530   52 if (now >= abs_time) 531   55 if (now >= abs_time)
HITCBC 531   26 return 0; 532   25 return 0;
532   } 533   }
533   } 534   }
534   535  
535   /** Process all ready work items without blocking. 536   /** Process all ready work items without blocking.
536   537  
537   This function executes all work items that are ready to run 538   This function executes all work items that are ready to run
538   without blocking for more work. The context is stopped 539   without blocking for more work. The context is stopped
539   when there is no more outstanding work. 540   when there is no more outstanding work.
540   541  
541   @note The context must be restarted with `restart()` before 542   @note The context must be restarted with `restart()` before
542   calling this function again after it returns. 543   calling this function again after it returns.
543   544  
544   @return The number of handlers executed. 545   @return The number of handlers executed.
545   */ 546   */
HITCBC 546   47 std::size_t poll() 547   47 std::size_t poll()
547   { 548   {
HITCBC 548   47 return sched_->poll(); 549   47 return sched_->poll();
549   } 550   }
550   551  
551   /** Process at most one ready work item without blocking. 552   /** Process at most one ready work item without blocking.
552   553  
553   This function executes at most one work item that is ready 554   This function executes at most one work item that is ready
554   to run without blocking for more work. The context is 555   to run without blocking for more work. The context is
555   stopped when there is no more outstanding work. 556   stopped when there is no more outstanding work.
556   557  
557   @note The context must be restarted with `restart()` before 558   @note The context must be restarted with `restart()` before
558   calling this function again after it returns. 559   calling this function again after it returns.
559   560  
560   @return The number of handlers executed (0 or 1). 561   @return The number of handlers executed (0 or 1).
561   */ 562   */
HITCBC 562   11 std::size_t poll_one() 563   11 std::size_t poll_one()
563   { 564   {
HITCBC 564   11 return sched_->poll_one(); 565   11 return sched_->poll_one();
565   } 566   }
566   }; 567   };
567   568  
568   /** An executor for dispatching work to an I/O context. 569   /** An executor for dispatching work to an I/O context.
569   570  
570   The executor provides the interface for posting work items and 571   The executor provides the interface for posting work items and
571   dispatching coroutines to the associated context. It satisfies 572   dispatching coroutines to the associated context. It satisfies
572   the `capy::Executor` concept. 573   the `capy::Executor` concept.
573   574  
574   Executors are lightweight handles that can be copied and compared 575   Executors are lightweight handles that can be copied and compared
575   for equality. Two executors compare equal if they refer to the 576   for equality. Two executors compare equal if they refer to the
576   same context. 577   same context.
577   578  
578   @par Thread Safety 579   @par Thread Safety
579   Distinct objects: Safe.@n 580   Distinct objects: Safe.@n
580   Shared objects: Safe. 581   Shared objects: Safe.
581   */ 582   */
582   class io_context::executor_type 583   class io_context::executor_type
583   { 584   {
584   io_context* ctx_ = nullptr; 585   io_context* ctx_ = nullptr;
585   586  
586   public: 587   public:
587   /** Default constructor. 588   /** Default constructor.
588   589  
589   Constructs an executor not associated with any context. 590   Constructs an executor not associated with any context.
590   */ 591   */
HITCBC 591   2053 executor_type() = default; 592   2053 executor_type() = default;
592   593  
593   /** Construct an executor from a context. 594   /** Construct an executor from a context.
594   595  
595   @param ctx The context to associate with this executor. 596   @param ctx The context to associate with this executor.
596   */ 597   */
HITCBC 597   4296 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {} 598   4296 explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
598   599  
599   /** Return a reference to the associated execution context. 600   /** Return a reference to the associated execution context.
600   601  
601   @return Reference to the context. 602   @return Reference to the context.
602   */ 603   */
HITCBC 603   26203 io_context& context() const noexcept 604   26023 io_context& context() const noexcept
604   { 605   {
HITCBC 605   26203 return *ctx_; 606   26023 return *ctx_;
606   } 607   }
607   608  
608   /** Check if the current thread is running this executor's context. 609   /** Check if the current thread is running this executor's context.
609   610  
610   @return `true` if `run()` is being called on this thread. 611   @return `true` if `run()` is being called on this thread.
611   */ 612   */
HITCBC 612   9444 bool running_in_this_thread() const noexcept 613   9435 bool running_in_this_thread() const noexcept
613   { 614   {
HITCBC 614   9444 return ctx_->sched_->running_in_this_thread(); 615   9435 return ctx_->sched_->running_in_this_thread();
615   } 616   }
616   617  
617   /** Informs the executor that work is beginning. 618   /** Informs the executor that work is beginning.
618   619  
619   Must be paired with `on_work_finished()`. 620   Must be paired with `on_work_finished()`.
620   */ 621   */
HITCBC 621   9651 void on_work_started() const noexcept 622   9642 void on_work_started() const noexcept
622   { 623   {
HITCBC 623   9651 ctx_->sched_->work_started(); 624   9642 ctx_->sched_->work_started();
HITCBC 624   9651 } 625   9642 }
625   626  
626   /** Informs the executor that work has completed. 627   /** Informs the executor that work has completed.
627   628  
628   @par Preconditions 629   @par Preconditions
629   A preceding call to `on_work_started()` on an equal executor. 630   A preceding call to `on_work_started()` on an equal executor.
630   */ 631   */
HITCBC 631   9589 void on_work_finished() const noexcept 632   9580 void on_work_finished() const noexcept
632   { 633   {
HITCBC 633   9589 ctx_->sched_->work_finished(); 634   9580 ctx_->sched_->work_finished();
HITCBC 634   9589 } 635   9580 }
635   636  
636   /** Dispatch a continuation. 637   /** Dispatch a continuation.
637   638  
638   Returns a handle for symmetric transfer. If called from 639   Returns a handle for symmetric transfer. If called from
639   within `run()`, returns `c.h`. Otherwise posts `c` for 640   within `run()`, returns `c.h`. Otherwise posts `c` for
640   later execution and returns `std::noop_coroutine()`. 641   later execution and returns `std::noop_coroutine()`.
641   642  
642   @param c The continuation to dispatch. 643   @param c The continuation to dispatch.
643   644  
644   @return A handle for symmetric transfer or `std::noop_coroutine()`. 645   @return A handle for symmetric transfer or `std::noop_coroutine()`.
645   646  
646   @par Preconditions 647   @par Preconditions
647   The associated context must outlive this call. Dispatching 648   The associated context must outlive this call. Dispatching
648   concurrently with, or after, the context's destruction is 649   concurrently with, or after, the context's destruction is
649   undefined behavior. 650   undefined behavior.
650   */ 651   */
HITCBC 651   9439 std::coroutine_handle<> dispatch(capy::continuation& c) const 652   9430 std::coroutine_handle<> dispatch(capy::continuation& c) const
652   { 653   {
HITCBC 653   9439 if (running_in_this_thread()) 654   9430 if (running_in_this_thread())
HITCBC 654   938 return c.h; 655   929 return c.h;
HITCBC 655   8501 post(c); 656   8501 post(c);
HITCBC 656   8501 return std::noop_coroutine(); 657   8501 return std::noop_coroutine();
657   } 658   }
658   659  
659   /** Post a continuation for deferred execution. 660   /** Post a continuation for deferred execution.
660   661  
661   Enqueues `c` directly on the scheduler's ready queue. 662   Enqueues `c` directly on the scheduler's ready queue.
662   No heap allocation occurs. 663   No heap allocation occurs.
663   664  
664   @par Preconditions 665   @par Preconditions
665   The associated context must outlive this call. Posting 666   The associated context must outlive this call. Posting
666   concurrently with, or after, the context's destruction is 667   concurrently with, or after, the context's destruction is
667   undefined behavior. 668   undefined behavior.
668   */ 669   */
HITCBC 669   23882 void post(capy::continuation& c) const 670   23711 void post(capy::continuation& c) const
670   { 671   {
HITCBC 671   23882 ctx_->sched_->post(c); 672   23711 ctx_->sched_->post(c);
HITCBC 672   23882 } 673   23711 }
673   674  
674   /** Post a bare coroutine handle for deferred execution. 675   /** Post a bare coroutine handle for deferred execution.
675   676  
676   Heap-allocates a scheduler_op to wrap the handle. A caller 677   Heap-allocates a scheduler_op to wrap the handle. A caller
677   that already owns a `scheduler_op` can post it directly via 678   that already owns a `scheduler_op` can post it directly via
678   the `post(scheduler_op*)` overload to avoid the allocation. 679   the `post(scheduler_op*)` overload to avoid the allocation.
679   680  
680   @param h The coroutine handle to post. 681   @param h The coroutine handle to post.
681   682  
682   @par Preconditions 683   @par Preconditions
683   The associated context must outlive this call. Posting 684   The associated context must outlive this call. Posting
684   concurrently with, or after, the context's destruction is 685   concurrently with, or after, the context's destruction is
685   undefined behavior. 686   undefined behavior.
686   */ 687   */
HITCBC 687   3756 void post(std::coroutine_handle<> h) const 688   3756 void post(std::coroutine_handle<> h) const
688   { 689   {
HITCBC 689   3756 ctx_->sched_->post(h); 690   3756 ctx_->sched_->post(h);
HITCBC 690   3756 } 691   3756 }
691   692  
692   /** Compare two executors for equality. 693   /** Compare two executors for equality.
693   694  
694   @return `true` if both executors refer to the same context. 695   @return `true` if both executors refer to the same context.
695   */ 696   */
HITCBC 696   2 bool operator==(executor_type const& other) const noexcept 697   2 bool operator==(executor_type const& other) const noexcept
697   { 698   {
HITCBC 698   2 return ctx_ == other.ctx_; 699   2 return ctx_ == other.ctx_;
699   } 700   }
700   701  
701   /** Compare two executors for inequality. 702   /** Compare two executors for inequality.
702   703  
703   @return `true` if the executors refer to different contexts. 704   @return `true` if the executors refer to different contexts.
704   */ 705   */
705   bool operator!=(executor_type const& other) const noexcept 706   bool operator!=(executor_type const& other) const noexcept
706   { 707   {
707   return ctx_ != other.ctx_; 708   return ctx_ != other.ctx_;
708   } 709   }
709   }; 710   };
710   711  
711   inline io_context::executor_type 712   inline io_context::executor_type
HITCBC 712   4296 io_context::get_executor() const noexcept 713   4296 io_context::get_executor() const noexcept
713   { 714   {
HITCBC 714   4296 return executor_type(const_cast<io_context&>(*this)); 715   4296 return executor_type(const_cast<io_context&>(*this));
715   } 716   }
716   717  
717   } // namespace boost::corosio 718   } // namespace boost::corosio
718   719  
719   #endif // BOOST_COROSIO_IO_CONTEXT_HPP 720   #endif // BOOST_COROSIO_IO_CONTEXT_HPP