include/boost/corosio/io_context.hpp

100.0% Lines (83 / 83) 100.0% Functions (28 / 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) :179 54x 100.0% 100.0% boost::corosio::io_context::io_context<boost::corosio::epoll_t>(boost::corosio::epoll_t, unsigned int) :315 952x 100.0% 86.0% boost::corosio::io_context::io_context<boost::corosio::select_t>(boost::corosio::select_t, unsigned int) :315 965x 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) :348 19x 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) :348 18x 100.0% 100.0% boost::corosio::io_context::stop() :385 13x 100.0% 100.0% boost::corosio::io_context::stopped() const :395 2480x 100.0% 100.0% boost::corosio::io_context::restart() :405 1419x 100.0% 100.0% boost::corosio::io_context::run() :421 1980x 100.0% 100.0% boost::corosio::io_context::run_one() :437 112x 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 11x 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 804x 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 816x 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 74x 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 2489x 100.0% 80.0% boost::corosio::io_context::poll() :557 47x 100.0% 100.0% boost::corosio::io_context::poll_one() :573 11x 100.0% 100.0% boost::corosio::io_context::executor_type::executor_type() :599 2053x 100.0% 100.0% boost::corosio::io_context::executor_type::executor_type(boost::corosio::io_context&) :605 5299x 100.0% 100.0% boost::corosio::io_context::executor_type::context() const :611 27641x 100.0% 100.0% boost::corosio::io_context::executor_type::running_in_this_thread() const :620 10795x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_started() const :629 11204x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_finished() const :638 11142x 100.0% 100.0% boost::corosio::io_context::executor_type::dispatch(boost::capy::continuation&) const :657 10790x 100.0% 100.0% boost::corosio::io_context::executor_type::post(boost::capy::continuation&) const :676 26092x 100.0% 100.0% boost::corosio::io_context::executor_type::post(std::__n4861::coroutine_handle<void>) const :694 3756x 100.0% 100.0% boost::corosio::io_context::executor_type::operator==(boost::corosio::io_context::executor_type const&) const :703 2x 100.0% 100.0% boost::corosio::io_context::get_executor() const :719 5299x 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 /** Selects which internal locks the scheduler and reactor elide,
30 trading thread-safety guarantees for reduced synchronization
31 overhead.
32
33 This is the analog of Boost.Asio's `SAFE` / `UNSAFE_IO` / `UNSAFE`
34 concurrency hint constants. The tier is chosen explicitly, not derived
35 from the `concurrency_hint`. (The reverse does apply: a lockless tier
36 reduces the 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`. A single thread must run
48 and drive the context. Resolver and POSIX file services remain
49 available, because they rely on scheduler locking, which stays
50 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 /** Configures scheduler and reactor tuning for an @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 @par !example configure
76
77 @see io_context, native_io_context
78 */
79 struct io_context_options
80 {
81 /** Maximum events fetched per reactor poll call.
82
83 Controls the buffer size passed to `epoll_wait()` or
84 `kevent()`. Larger values reduce syscall frequency under
85 high load. Smaller values improve fairness between
86 connections. Ignored on IOCP and select backends.
87 */
88 unsigned max_events_per_poll = 128;
89
90 /** Starting inline completion budget per handler chain.
91
92 After a posted handler executes, the reactor grants this
93 many speculative inline completions before forcing a
94 re-queue. Applies to reactor backends only.
95
96 @note Constructing an `io_context` with `concurrency_hint > 1`
97 and all three budget fields at their defaults overrides them to
98 disable inline completion, giving post-everything mode.
99 Multi-thread workloads benefit from cross-thread work-stealing.
100 Setting any budget field to a non-default
101 value disables the override.
102 */
103 unsigned inline_budget_initial = 2;
104
105 /** Hard ceiling on adaptive inline budget ramp-up.
106
107 The budget doubles each cycle it is fully consumed, up to
108 this limit. Applies to reactor backends only.
109 */
110 unsigned inline_budget_max = 16;
111
112 /** Inline budget when no other thread assists the reactor.
113
114 When only one thread is running the event loop, this
115 value caps the inline budget to preserve fairness.
116 Applies to reactor backends only.
117 */
118 unsigned unassisted_budget = 4;
119
120 /** Thread pool size for blocking I/O (file I/O, DNS resolution).
121
122 Sets the number of worker threads in the shared thread pool
123 used by POSIX file services and DNS resolution. Must be at
124 least 1. Applies to POSIX backends only; ignored on IOCP
125 where file I/O uses native overlapped I/O.
126 */
127 unsigned thread_pool_size = 1;
128
129 /** Thread-safety tier. See @ref locking_mode for the tiers and their
130 restrictions.
131 */
132 locking_mode locking = locking_mode::safe;
133
134 /** Enable IORING_SETUP_SQPOLL on the io_uring backend.
135
136 With SQPOLL, the kernel forks a thread that busy-polls the
137 submission ring. Submission becomes a userspace-only memory
138 store, which eliminates the `io_uring_enter` syscall on the submit
139 path. Most useful for sustained traffic. Idle thread parks
140 after `sq_thread_idle_ms` of no activity.
141
142 Independent of `locking`. Default: off.
143
144 Ignored on non-io_uring backends.
145 */
146 bool enable_sqpoll = false;
147
148 /** SQ-poll idle timeout in milliseconds.
149
150 After this many ms of no submissions, the kernel polling
151 thread sleeps. The next submit re-wakes it via SQ_WAKEUP. 0
152 means use the kernel default (1ms). Recommended for bursty
153 workloads: 100-1000ms (avoids park/unpark thrash).
154
155 Ignored unless `enable_sqpoll` is true. Ignored on
156 non-io_uring backends.
157 */
158 unsigned sq_thread_idle_ms = 0;
159
160 /** Pin the SQ-poll kernel thread to this CPU.
161
162 -1 means do not pin (kernel scheduler picks). Pinning off
163 the dispatch core is recommended on latency-sensitive
164 deployments to avoid cache contention.
165
166 Ignored unless `enable_sqpoll` is true. Ignored on
167 non-io_uring backends.
168 */
169 int sq_thread_cpu = -1;
170 };
171
172 namespace detail {
173 class timer_service;
174
175 /** Return the hint used for performance tuning: the lockless tiers are
176 single-threaded, so their effective hint is 1 whatever the caller passed.
177 */
178 inline unsigned
179 54x effective_concurrency_hint(
180 io_context_options const& opts, unsigned hint) noexcept
181 {
182 54x return opts.locking == locking_mode::safe ? hint : 1u;
183 }
184 } // namespace detail
185
186 /** Runs asynchronous operations and owns the I/O backend that drives them.
187
188 The `io_context` provides an execution environment for async
189 operations. It maintains a queue of pending work items and
190 processes them when `run()` is called.
191
192 The default and unsigned constructors select the platform's
193 native backend:
194 - Windows: IOCP
195 - Linux: epoll
196 - BSD/macOS: kqueue
197 - Other POSIX: select
198
199 The template constructor accepts a backend tag value to
200 choose a specific backend at compile time:
201
202 @par Example
203 @par !example construct
204
205 @pre The context must outlive every operation posted or dispatched
206 through its executor. No thread may be executing a run variant when
207 the context is destroyed. Posting to the context
208 concurrently with, or after, its destruction is undefined
209 behavior. For a safe teardown, first stop submitting new work.
210 Then let every `run()` call return; each returns once no
211 outstanding work remains. Finally join the threads that ran the
212 loop. Only then destroy the context. Work started with
213 `capy::run` / `capy::run_async` is work-tracked, so a normal
214 `run()` completion already waits for it.
215
216 @par Exception Safety
217 A context that constructs is usable. The infrastructure its backend
218 needs — the completion port, the ring, the reactor's wakeup channel
219 — is created during construction. A system that refuses it therefore
220 throws from the constructor rather than from the first operation.
221 The failed construction leaves nothing open.
222
223 @par Thread Safety
224 Distinct objects: Safe.@n
225 Shared objects: Safe, unless the context was constructed with a
226 lockless @ref io_context_options::locking tier (`unsafe_io` or
227 `unsafe`), in which case a single thread must drive it.
228
229 @see epoll_t, select_t, kqueue_t, iocp_t
230 */
231 class BOOST_COROSIO_DECL io_context : public capy::execution_context
232 {
233 /// Reject invalid options before the backend is constructed.
234 void apply_options_pre_(io_context_options const& opts);
235
236 /** Create the blocking-I/O thread pool, apply runtime tuning to the
237 scheduler and finish bringing the backend up. The tail of every
238 options constructor. The backend infrastructure whose setup reads
239 these options is created here, so a failure to create it throws
240 from the constructor. */
241 void apply_options_post_(
242 io_context_options const& opts, unsigned concurrency_hint);
243
244 /** Create the blocking-I/O thread pool and apply only the decomposed
245 threading configuration (locking tiers), then finish bringing the
246 backend up. The tail of every plain constructor. Unlike the
247 options constructors, it deliberately leaves the reactor budget
248 at its defaults rather than engaging the multi-thread
249 post-everything heuristic. */
250 void apply_threading_(io_context_options const& opts);
251
252 protected:
253 detail::scheduler* sched_;
254
255 public:
256 /** Dispatches and posts work to this context; see the
257 executor_type definition below. */
258 class executor_type;
259
260 /** Construct with default concurrency and platform backend.
261
262 Uses `std::thread::hardware_concurrency()` (floored to 1, in
263 case it reports 0) as the concurrency hint, and the default
264 @ref locking_mode::safe tier. Select a lockless tier via
265 @ref io_context_options::locking.
266
267 @throws std::system_error If the backend's infrastructure
268 could not be created.
269 */
270 io_context();
271
272 /** Construct with a concurrency hint and platform backend.
273
274 @param concurrency_hint Hint for the number of threads
275 that calls `run()`.
276
277 @throws std::system_error If the backend's infrastructure
278 could not be created.
279 */
280 explicit io_context(unsigned concurrency_hint);
281
282 /** Construct with runtime tuning options and platform backend.
283
284 @param opts Runtime options controlling scheduler and
285 service behavior.
286 @param concurrency_hint Hint for the number of threads
287 that calls `run()`.
288
289 @throws std::invalid_argument If `opts.thread_pool_size` is
290 less than 1 (POSIX).
291
292 @throws std::system_error If the backend's infrastructure
293 could not be created.
294 */
295 explicit io_context(
296 io_context_options const& opts,
297 unsigned concurrency_hint = std::thread::hardware_concurrency());
298
299 /** Construct with an explicit backend tag.
300
301 @tparam Backend A backend tag type that provides a static
302 `construct(capy::execution_context&, unsigned)` factory
303 used to build the scheduler.
304
305 @param backend The backend tag value selecting the I/O
306 multiplexer (e.g. `corosio::epoll`).
307 @param concurrency_hint Hint for the number of threads
308 that calls `run()`.
309
310 @throws std::system_error If the backend's infrastructure
311 could not be created.
312 */
313 template<class Backend>
314 requires requires { Backend::construct; }
315 1917x explicit io_context(
316 [[maybe_unused]] Backend backend,
317 unsigned concurrency_hint = std::thread::hardware_concurrency())
318 : capy::execution_context(this)
319 1917x , sched_(nullptr)
320 {
321 1917x sched_ = &Backend::construct(*this, concurrency_hint);
322 // Apply threading config only (locking tier). Unlike the options
323 // ctor, the plain path leaves the reactor budget at its defaults.
324 1905x apply_threading_(io_context_options{});
325 1917x }
326
327 /** Construct with an explicit backend tag and runtime options.
328
329 @tparam Backend A backend tag type that provides a static
330 `construct(capy::execution_context&, unsigned)` factory
331 used to build the scheduler.
332
333 @param backend The backend tag value selecting the I/O
334 multiplexer (e.g. `corosio::epoll`).
335 @param opts Runtime options controlling scheduler and
336 service behavior.
337 @param concurrency_hint Hint for the number of threads
338 that calls `run()`.
339
340 @throws std::invalid_argument If `opts.thread_pool_size` is
341 less than 1 (POSIX).
342
343 @throws std::system_error If the backend's infrastructure
344 could not be created.
345 */
346 template<class Backend>
347 requires requires { Backend::construct; }
348 37x explicit io_context(
349 [[maybe_unused]] Backend backend,
350 io_context_options const& opts,
351 unsigned concurrency_hint = std::thread::hardware_concurrency())
352 : capy::execution_context(this)
353 37x , sched_(nullptr)
354 {
355 37x apply_options_pre_(opts);
356 // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
357 unsigned const eff =
358 37x detail::effective_concurrency_hint(opts, concurrency_hint);
359 37x sched_ = &Backend::construct(*this, eff);
360 37x apply_options_post_(opts, eff);
361 37x }
362
363 /// Destroy the context; stops the loop and destroys every service.
364 ~io_context();
365
366 /// Copy construction is disabled; the context owns its services.
367 io_context(io_context const&) = delete;
368 /// Copy assignment is disabled; the context owns its services.
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 stopped.
391
392 @return `true` after a call to `stop()` with no later
393 call to `restart()`.
394 */
395 2480x bool stopped() const noexcept
396 {
397 2480x 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 a call to `stop()`.
404 */
405 1419x void restart()
406 {
407 1419x sched_->restart();
408 1419x }
409
410 /** Process all pending work items.
411
412 This function blocks until it executes all pending work items,
413 or until `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 1980x std::size_t run()
422 {
423 1980x return sched_->run();
424 }
425
426 /** Process at most one pending work item.
427
428 This function blocks until it executes one work item
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 112x std::size_t run_one()
438 {
439 112x return sched_->run_one();
440 }
441
442 /** Process work items for the specified duration.
443
444 This function blocks until it has executed work items for the
445 specified duration, or until `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 815x std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
457 {
458 815x 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 816x run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
477 {
478 816x std::size_t n = 0;
479 2407x while (run_one_until(abs_time))
480 1591x if (n != (std::numeric_limits<std::size_t>::max)())
481 1591x ++n;
482 816x return n;
483 }
484
485 /** Process at most one work item for the specified duration.
486
487 This function blocks until it executes one work item,
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 74x std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
500 {
501 74x 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 it executes one work item,
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 2489x run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
520 {
521 2489x typename Clock::time_point now = Clock::now();
522 1591x for (;;)
523 {
524 4080x auto rel_time = abs_time - now;
525 using rel_type = decltype(rel_time);
526 4080x if (rel_time < rel_type::zero())
527 5x rel_time = rel_type::zero();
528 4075x else if (rel_time > std::chrono::seconds(1))
529 3966x rel_time = std::chrono::seconds(1);
530
531 4080x std::size_t s = sched_->wait_one(
532 static_cast<long>(
533 4080x std::chrono::duration_cast<std::chrono::microseconds>(
534 rel_time)
535 4080x .count()));
536
537 4080x if (s || stopped())
538 2489x return s;
539
540 1616x now = Clock::now();
541 1616x if (now >= abs_time)
542 25x 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 47x std::size_t poll()
558 {
559 47x 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 11x std::size_t poll_one()
574 {
575 11x return sched_->poll_one();
576 }
577 };
578
579 /** Dispatches and posts 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 /** Constructs an executor not associated with any context. */
599 2053x executor_type() = default;
600
601 /** Construct an executor from a context.
602
603 @param ctx The context to associate with this executor.
604 */
605 5299x explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
606
607 /** Return a reference to the associated execution context.
608
609 @return Reference to the context.
610 */
611 27641x io_context& context() const noexcept
612 {
613 27641x return *ctx_;
614 }
615
616 /** Check if the current thread is running this executor's context.
617
618 @return `true` if `run()` is being called on this thread.
619 */
620 10795x bool running_in_this_thread() const noexcept
621 {
622 10795x return ctx_->sched_->running_in_this_thread();
623 }
624
625 /** Informs the executor that work is beginning.
626
627 Must be paired with `on_work_finished()`.
628 */
629 11204x void on_work_started() const noexcept
630 {
631 11204x ctx_->sched_->work_started();
632 11204x }
633
634 /** Informs the executor that work has completed.
635
636 @pre A preceding call to `on_work_started()` on an equal executor.
637 */
638 11142x void on_work_finished() const noexcept
639 {
640 11142x ctx_->sched_->work_finished();
641 11142x }
642
643 /** Dispatch a continuation.
644
645 Returns a handle for symmetric transfer. If called from
646 within `run()`, returns `c.h`. Otherwise posts `c` for
647 later execution and returns `std::noop_coroutine()`.
648
649 @param c The continuation to dispatch.
650
651 @return A handle for symmetric transfer or `std::noop_coroutine()`.
652
653 @pre The associated context must outlive this call. Dispatching
654 concurrently with, or after, the context's destruction is
655 undefined behavior.
656 */
657 10790x std::coroutine_handle<> dispatch(capy::continuation& c) const
658 {
659 10790x if (running_in_this_thread())
660 944x return c.h;
661 9846x post(c);
662 9846x return std::noop_coroutine();
663 }
664
665 /** Post a continuation for deferred execution.
666
667 Enqueues `c` directly on the scheduler's ready queue.
668 No heap allocation occurs.
669
670 @param c The continuation to enqueue.
671
672 @pre The associated context must outlive this call. Posting
673 concurrently with, or after, the context's destruction is
674 undefined behavior.
675 */
676 26092x void post(capy::continuation& c) const
677 {
678 26092x ctx_->sched_->post(c);
679 26092x }
680
681 /** Post a bare coroutine handle for deferred execution.
682
683 Heap-allocates a `scheduler_op` to wrap the handle. A caller
684 that already owns a `capy::continuation` can post it directly
685 via the `post(capy::continuation&)` overload to avoid the
686 allocation.
687
688 @param h The coroutine handle to post.
689
690 @pre The associated context must outlive this call. Posting
691 concurrently with, or after, the context's destruction is
692 undefined behavior.
693 */
694 3756x void post(std::coroutine_handle<> h) const
695 {
696 3756x ctx_->sched_->post(h);
697 3756x }
698
699 /** Compare two executors for equality.
700
701 @return `true` if both executors refer to the same context.
702 */
703 2x bool operator==(executor_type const& other) const noexcept
704 {
705 2x return ctx_ == other.ctx_;
706 }
707
708 /** Compare two executors for inequality.
709
710 @return `true` if the executors refer to different contexts.
711 */
712 bool operator!=(executor_type const& other) const noexcept
713 {
714 return ctx_ != other.ctx_;
715 }
716 };
717
718 inline io_context::executor_type
719 5299x io_context::get_executor() const noexcept
720 {
721 5299x return executor_type(const_cast<io_context&>(*this));
722 }
723
724 } // namespace boost::corosio
725
726 #endif // BOOST_COROSIO_IO_CONTEXT_HPP
727