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