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