include/boost/corosio/io_context.hpp

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