include/boost/corosio/io_context.hpp

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