include/boost/corosio/io_context.hpp

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