include/boost/corosio/io_context.hpp

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