include/boost/corosio/io_context.hpp

97.3% Lines (72/74) 100.0% List of functions (23/23)
io_context.hpp
f(x) Functions (23)
Function Calls Lines Blocks
boost::corosio::io_context::io_context<boost::corosio::epoll_t>(boost::corosio::epoll_t, unsigned int) :225 239x 83.3% 71.0% boost::corosio::io_context::io_context<boost::corosio::select_t>(boost::corosio::select_t, unsigned int) :225 239x 83.3% 71.0% boost::corosio::io_context::stop() :280 5x 100.0% 100.0% boost::corosio::io_context::stopped() const :290 62x 100.0% 100.0% boost::corosio::io_context::restart() :300 111x 100.0% 100.0% boost::corosio::io_context::run() :316 432x 100.0% 100.0% boost::corosio::io_context::run_one() :332 2x 100.0% 100.0% unsigned long boost::corosio::io_context::run_for<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) :351 9x 100.0% 88.0% unsigned long boost::corosio::io_context::run_until<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > const&) :371 9x 100.0% 100.0% unsigned long boost::corosio::io_context::run_one_for<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) :394 3x 100.0% 88.0% unsigned long boost::corosio::io_context::run_one_until<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > const&) :414 63x 92.3% 72.0% boost::corosio::io_context::poll() :448 6x 100.0% 100.0% boost::corosio::io_context::poll_one() :464 4x 100.0% 100.0% boost::corosio::io_context::executor_type::executor_type(boost::corosio::io_context&) :499 668x 100.0% 100.0% boost::corosio::io_context::executor_type::context() const :505 1347x 100.0% 100.0% boost::corosio::io_context::executor_type::running_in_this_thread() const :514 1359x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_started() const :523 1504x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_finished() const :533 1478x 100.0% 100.0% boost::corosio::io_context::executor_type::dispatch(boost::capy::continuation&) const :550 1357x 100.0% 100.0% boost::corosio::io_context::executor_type::post(boost::capy::continuation&) const :565 9340x 100.0% 100.0% boost::corosio::io_context::executor_type::post(std::__n4861::coroutine_handle<void>) const :582 1426x 100.0% 100.0% boost::corosio::io_context::executor_type::operator==(boost::corosio::io_context::executor_type const&) const :591 1x 100.0% 100.0% boost::corosio::io_context::get_executor() const :607 668x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Steve Gerbino
4 // Copyright (c) 2026 Michael Vandeberg
5 //
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)
8 //
9 // Official repository: https://github.com/cppalliance/corosio
10 //
11
12 #ifndef BOOST_COROSIO_IO_CONTEXT_HPP
13 #define BOOST_COROSIO_IO_CONTEXT_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/detail/continuation_op.hpp>
17 #include <boost/corosio/detail/platform.hpp>
18 #include <boost/corosio/detail/scheduler.hpp>
19 #include <boost/capy/continuation.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21
22 #include <chrono>
23 #include <coroutine>
24 #include <cstddef>
25 #include <limits>
26 #include <thread>
27
28 namespace boost::corosio {
29
30 /** Runtime tuning options for @ref io_context.
31
32 All fields have defaults that match the library's built-in
33 values, so constructing a default `io_context_options` produces
34 identical behavior to an unconfigured context.
35
36 Options that apply only to a specific backend family are
37 silently ignored when the active backend does not support them.
38
39 @par Example
40 @code
41 io_context_options opts;
42 opts.max_events_per_poll = 256; // larger batch per syscall
43 opts.inline_budget_max = 32; // more speculative completions
44 opts.thread_pool_size = 4; // more file-I/O workers
45
46 io_context ioc(opts);
47 @endcode
48
49 @see io_context, native_io_context
50 */
51 struct io_context_options
52 {
53 /** Maximum events fetched per reactor poll call.
54
55 Controls the buffer size passed to `epoll_wait()` or
56 `kevent()`. Larger values reduce syscall frequency under
57 high load; smaller values improve fairness between
58 connections. Ignored on IOCP and select backends.
59 */
60 unsigned max_events_per_poll = 128;
61
62 /** Starting inline completion budget per handler chain.
63
64 After a posted handler executes, the reactor grants this
65 many speculative inline completions before forcing a
66 re-queue. Applies to reactor backends only.
67
68 @note Constructing an `io_context` with `concurrency_hint > 1`
69 and all three budget fields at their defaults overrides
70 them to disable inline completion (post-everything mode),
71 since multi-thread workloads benefit from cross-thread
72 work-stealing. Setting any budget field to a non-default
73 value disables the override.
74 */
75 unsigned inline_budget_initial = 2;
76
77 /** Hard ceiling on adaptive inline budget ramp-up.
78
79 The budget doubles each cycle it is fully consumed, up to
80 this limit. Applies to reactor backends only.
81 */
82 unsigned inline_budget_max = 16;
83
84 /** Inline budget when no other thread assists the reactor.
85
86 When only one thread is running the event loop, this
87 value caps the inline budget to preserve fairness.
88 Applies to reactor backends only.
89 */
90 unsigned unassisted_budget = 4;
91
92 /** Maximum `GetQueuedCompletionStatus` timeout in milliseconds.
93
94 Bounds how long the IOCP scheduler blocks between timer
95 rechecks. Lower values improve timer responsiveness at the
96 cost of more syscalls. Applies to IOCP only.
97 */
98 unsigned gqcs_timeout_ms = 500;
99
100 /** Thread pool size for blocking I/O (file I/O, DNS resolution).
101
102 Sets the number of worker threads in the shared thread pool
103 used by POSIX file services and DNS resolution. Must be at
104 least 1. Applies to POSIX backends only; ignored on IOCP
105 where file I/O uses native overlapped I/O.
106 */
107 unsigned thread_pool_size = 1;
108
109 /** Enable single-threaded mode (disable scheduler locking).
110
111 When true, the scheduler skips all mutex lock/unlock and
112 condition variable operations on the hot path. This
113 eliminates synchronization overhead when only one thread
114 calls `run()`.
115
116 @par Restrictions
117 - Only one thread may call `run()` (or any run variant).
118 - Posting work from another thread is undefined behavior.
119 - DNS resolution returns `operation_not_supported`.
120 - POSIX file I/O returns `operation_not_supported`.
121 - Signal sets should not be shared across contexts.
122
123 @note Constructing an `io_context` with `concurrency_hint == 1`
124 automatically enables single-threaded mode regardless of
125 this field's value, matching asio's convention. To opt out,
126 pass `concurrency_hint > 1`.
127 */
128 bool single_threaded = false;
129 };
130
131 namespace detail {
132 class timer_service;
133 struct timer_service_access;
134 } // namespace detail
135
136 /** An I/O context for running asynchronous operations.
137
138 The io_context provides an execution environment for async
139 operations. It maintains a queue of pending work items and
140 processes them when `run()` is called.
141
142 The default and unsigned constructors select the platform's
143 native backend:
144 - Windows: IOCP
145 - Linux: epoll
146 - BSD/macOS: kqueue
147 - Other POSIX: select
148
149 The template constructor accepts a backend tag value to
150 choose a specific backend at compile time:
151
152 @par Example
153 @code
154 io_context ioc; // platform default
155 io_context ioc2(corosio::epoll); // explicit backend
156 @endcode
157
158 @par Thread Safety
159 Distinct objects: Safe.@n
160 Shared objects: Safe, if using a concurrency hint greater
161 than 1.
162
163 @see epoll_t, select_t, kqueue_t, iocp_t
164 */
165 class BOOST_COROSIO_DECL io_context : public capy::execution_context
166 {
167 friend struct detail::timer_service_access;
168
169 /// Pre-create services that depend on options (before construct).
170 void apply_options_pre_(io_context_options const& opts);
171
172 /// Apply runtime tuning to the scheduler (after construct).
173 void apply_options_post_(
174 io_context_options const& opts,
175 unsigned concurrency_hint);
176
177 /// Switch the scheduler to single-threaded (lockless) mode.
178 void configure_single_threaded_();
179
180 protected:
181 detail::timer_service* timer_svc_ = nullptr;
182 detail::scheduler* sched_;
183
184 public:
185 /** The executor type for this context. */
186 class executor_type;
187
188 /** Construct with default concurrency and platform backend.
189
190 Uses `std::thread::hardware_concurrency()` clamped to a minimum
191 of 2 as the concurrency hint, so the default constructor never
192 silently engages single-threaded mode (see
193 @ref io_context_options::single_threaded). Pass an explicit
194 `concurrency_hint == 1` to opt into single-threaded mode.
195 */
196 io_context();
197
198 /** Construct with a concurrency hint and platform backend.
199
200 @param concurrency_hint Hint for the number of threads
201 that will call `run()`.
202 */
203 explicit io_context(unsigned concurrency_hint);
204
205 /** Construct with runtime tuning options and platform backend.
206
207 @param opts Runtime options controlling scheduler and
208 service behavior.
209 @param concurrency_hint Hint for the number of threads
210 that will call `run()`.
211 */
212 explicit io_context(
213 io_context_options const& opts,
214 unsigned concurrency_hint = std::thread::hardware_concurrency());
215
216 /** Construct with an explicit backend tag.
217
218 @param backend The backend tag value selecting the I/O
219 multiplexer (e.g. `corosio::epoll`).
220 @param concurrency_hint Hint for the number of threads
221 that will call `run()`.
222 */
223 template<class Backend>
224 requires requires { Backend::construct; }
225 478x explicit io_context(
226 Backend backend,
227 unsigned concurrency_hint = std::thread::hardware_concurrency())
228 : capy::execution_context(this)
229 478x , sched_(nullptr)
230 {
231 (void)backend;
232 478x sched_ = &Backend::construct(*this, concurrency_hint);
233 478x if (concurrency_hint == 1)
234 configure_single_threaded_();
235 478x }
236
237 /** Construct with an explicit backend tag and runtime options.
238
239 @param backend The backend tag value selecting the I/O
240 multiplexer (e.g. `corosio::epoll`).
241 @param opts Runtime options controlling scheduler and
242 service behavior.
243 @param concurrency_hint Hint for the number of threads
244 that will call `run()`.
245 */
246 template<class Backend>
247 requires requires { Backend::construct; }
248 explicit io_context(
249 Backend backend,
250 io_context_options const& opts,
251 unsigned concurrency_hint = std::thread::hardware_concurrency())
252 : capy::execution_context(this)
253 , sched_(nullptr)
254 {
255 (void)backend;
256 apply_options_pre_(opts);
257 sched_ = &Backend::construct(*this, concurrency_hint);
258 apply_options_post_(opts, concurrency_hint);
259 }
260
261 ~io_context();
262
263 io_context(io_context const&) = delete;
264 io_context& operator=(io_context const&) = delete;
265
266 /** Return an executor for this context.
267
268 The returned executor can be used to dispatch coroutines
269 and post work items to this context.
270
271 @return An executor associated with this context.
272 */
273 executor_type get_executor() const noexcept;
274
275 /** Signal the context to stop processing.
276
277 This causes `run()` to return as soon as possible. Any pending
278 work items remain queued.
279 */
280 5x void stop()
281 {
282 5x sched_->stop();
283 5x }
284
285 /** Return whether the context has been stopped.
286
287 @return `true` if `stop()` has been called and `restart()`
288 has not been called since.
289 */
290 62x bool stopped() const noexcept
291 {
292 62x return sched_->stopped();
293 }
294
295 /** Restart the context after being stopped.
296
297 This function must be called before `run()` can be called
298 again after `stop()` has been called.
299 */
300 111x void restart()
301 {
302 111x sched_->restart();
303 111x }
304
305 /** Process all pending work items.
306
307 This function blocks until all pending work items have been
308 executed or `stop()` is called. The context is stopped
309 when there is no more outstanding work.
310
311 @note The context must be restarted with `restart()` before
312 calling this function again after it returns.
313
314 @return The number of handlers executed.
315 */
316 432x std::size_t run()
317 {
318 432x return sched_->run();
319 }
320
321 /** Process at most one pending work item.
322
323 This function blocks until one work item has been executed
324 or `stop()` is called. The context is stopped when there
325 is no more outstanding work.
326
327 @note The context must be restarted with `restart()` before
328 calling this function again after it returns.
329
330 @return The number of handlers executed (0 or 1).
331 */
332 2x std::size_t run_one()
333 {
334 2x return sched_->run_one();
335 }
336
337 /** Process work items for the specified duration.
338
339 This function blocks until work items have been executed for
340 the specified duration, or `stop()` is called. The context
341 is stopped when there is no more outstanding work.
342
343 @note The context must be restarted with `restart()` before
344 calling this function again after it returns.
345
346 @param rel_time The duration for which to process work.
347
348 @return The number of handlers executed.
349 */
350 template<class Rep, class Period>
351 9x std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
352 {
353 9x return run_until(std::chrono::steady_clock::now() + rel_time);
354 }
355
356 /** Process work items until the specified time.
357
358 This function blocks until the specified time is reached
359 or `stop()` is called. The context is stopped when there
360 is no more outstanding work.
361
362 @note The context must be restarted with `restart()` before
363 calling this function again after it returns.
364
365 @param abs_time The time point until which to process work.
366
367 @return The number of handlers executed.
368 */
369 template<class Clock, class Duration>
370 std::size_t
371 9x run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
372 {
373 9x std::size_t n = 0;
374 58x while (run_one_until(abs_time))
375 49x if (n != (std::numeric_limits<std::size_t>::max)())
376 49x ++n;
377 9x return n;
378 }
379
380 /** Process at most one work item for the specified duration.
381
382 This function blocks until one work item has been executed,
383 the specified duration has elapsed, or `stop()` is called.
384 The context is stopped when there is no more outstanding work.
385
386 @note The context must be restarted with `restart()` before
387 calling this function again after it returns.
388
389 @param rel_time The duration for which the call may block.
390
391 @return The number of handlers executed (0 or 1).
392 */
393 template<class Rep, class Period>
394 3x std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
395 {
396 3x return run_one_until(std::chrono::steady_clock::now() + rel_time);
397 }
398
399 /** Process at most one work item until the specified time.
400
401 This function blocks until one work item has been executed,
402 the specified time is reached, or `stop()` is called.
403 The context is stopped when there is no more outstanding work.
404
405 @note The context must be restarted with `restart()` before
406 calling this function again after it returns.
407
408 @param abs_time The time point until which the call may block.
409
410 @return The number of handlers executed (0 or 1).
411 */
412 template<class Clock, class Duration>
413 std::size_t
414 63x run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
415 {
416 63x typename Clock::time_point now = Clock::now();
417 104x while (now < abs_time)
418 {
419 102x auto rel_time = abs_time - now;
420 102x if (rel_time > std::chrono::seconds(1))
421 rel_time = std::chrono::seconds(1);
422
423 102x std::size_t s = sched_->wait_one(
424 static_cast<long>(
425 102x std::chrono::duration_cast<std::chrono::microseconds>(
426 rel_time)
427 102x .count()));
428
429 102x if (s || stopped())
430 61x return s;
431
432 41x now = Clock::now();
433 }
434 2x return 0;
435 }
436
437 /** Process all ready work items without blocking.
438
439 This function executes all work items that are ready to run
440 without blocking for more work. The context is stopped
441 when there is no more outstanding work.
442
443 @note The context must be restarted with `restart()` before
444 calling this function again after it returns.
445
446 @return The number of handlers executed.
447 */
448 6x std::size_t poll()
449 {
450 6x return sched_->poll();
451 }
452
453 /** Process at most one ready work item without blocking.
454
455 This function executes at most one work item that is ready
456 to run without blocking for more work. The context is
457 stopped when there is no more outstanding work.
458
459 @note The context must be restarted with `restart()` before
460 calling this function again after it returns.
461
462 @return The number of handlers executed (0 or 1).
463 */
464 4x std::size_t poll_one()
465 {
466 4x return sched_->poll_one();
467 }
468 };
469
470 /** An executor for dispatching work to an I/O context.
471
472 The executor provides the interface for posting work items and
473 dispatching coroutines to the associated context. It satisfies
474 the `capy::Executor` concept.
475
476 Executors are lightweight handles that can be copied and compared
477 for equality. Two executors compare equal if they refer to the
478 same context.
479
480 @par Thread Safety
481 Distinct objects: Safe.@n
482 Shared objects: Safe.
483 */
484 class io_context::executor_type
485 {
486 io_context* ctx_ = nullptr;
487
488 public:
489 /** Default constructor.
490
491 Constructs an executor not associated with any context.
492 */
493 executor_type() = default;
494
495 /** Construct an executor from a context.
496
497 @param ctx The context to associate with this executor.
498 */
499 668x explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
500
501 /** Return a reference to the associated execution context.
502
503 @return Reference to the context.
504 */
505 1347x io_context& context() const noexcept
506 {
507 1347x return *ctx_;
508 }
509
510 /** Check if the current thread is running this executor's context.
511
512 @return `true` if `run()` is being called on this thread.
513 */
514 1359x bool running_in_this_thread() const noexcept
515 {
516 1359x return ctx_->sched_->running_in_this_thread();
517 }
518
519 /** Informs the executor that work is beginning.
520
521 Must be paired with `on_work_finished()`.
522 */
523 1504x void on_work_started() const noexcept
524 {
525 1504x ctx_->sched_->work_started();
526 1504x }
527
528 /** Informs the executor that work has completed.
529
530 @par Preconditions
531 A preceding call to `on_work_started()` on an equal executor.
532 */
533 1478x void on_work_finished() const noexcept
534 {
535 1478x ctx_->sched_->work_finished();
536 1478x }
537
538 /** Dispatch a continuation.
539
540 Returns a handle for symmetric transfer. If called from
541 within `run()`, returns `c.h`. Otherwise posts the
542 enclosing continuation_op as a scheduler_op for later
543 execution and returns `std::noop_coroutine()`.
544
545 @param c The continuation to dispatch. Must be the `cont`
546 member of a `detail::continuation_op`.
547
548 @return A handle for symmetric transfer or `std::noop_coroutine()`.
549 */
550 1357x std::coroutine_handle<> dispatch(capy::continuation& c) const
551 {
552 1357x if (running_in_this_thread())
553 597x return c.h;
554 760x post(c);
555 760x return std::noop_coroutine();
556 }
557
558 /** Post a continuation for deferred execution.
559
560 If the continuation is backed by a continuation_op
561 (tagged), posts it directly as a scheduler_op — zero
562 heap allocation. Otherwise falls back to the
563 heap-allocating post(coroutine_handle<>) path.
564 */
565 9340x void post(capy::continuation& c) const
566 {
567 9340x auto* op = detail::continuation_op::try_from_continuation(c);
568 9340x if (op)
569 8577x ctx_->sched_->post(op);
570 else
571 763x ctx_->sched_->post(c.h);
572 9340x }
573
574 /** Post a bare coroutine handle for deferred execution.
575
576 Heap-allocates a scheduler_op to wrap the handle. Prefer
577 posting through a continuation_op-backed continuation when
578 the continuation has suitable lifetime.
579
580 @param h The coroutine handle to post.
581 */
582 1426x void post(std::coroutine_handle<> h) const
583 {
584 1426x ctx_->sched_->post(h);
585 1426x }
586
587 /** Compare two executors for equality.
588
589 @return `true` if both executors refer to the same context.
590 */
591 1x bool operator==(executor_type const& other) const noexcept
592 {
593 1x return ctx_ == other.ctx_;
594 }
595
596 /** Compare two executors for inequality.
597
598 @return `true` if the executors refer to different contexts.
599 */
600 bool operator!=(executor_type const& other) const noexcept
601 {
602 return ctx_ != other.ctx_;
603 }
604 };
605
606 inline io_context::executor_type
607 668x io_context::get_executor() const noexcept
608 {
609 668x return executor_type(const_cast<io_context&>(*this));
610 }
611
612 } // namespace boost::corosio
613
614 #endif // BOOST_COROSIO_IO_CONTEXT_HPP
615