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 11607 : 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 11607 : ~frame_allocator_restore()
278 : {
279 11607 : capy::set_current_frame_allocator(saved);
280 11607 : }
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 3839 : explicit io_context(
344 : [[maybe_unused]] Backend backend,
345 : unsigned concurrency_hint = std::thread::hardware_concurrency())
346 : : capy::execution_context(this)
347 3839 : , sched_(nullptr)
348 : {
349 3839 : 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 3827 : apply_threading_(io_context_options{});
353 3839 : }
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 Work posted after the services have shut down, for example
411 : from a service's destructor, is abandoned while the services
412 : are being destroyed. Its destructors must not use them.
413 : */
414 : ~io_context();
415 :
416 : /// Copy construction is disabled; the context owns its services.
417 : io_context(io_context const&) = delete;
418 : /// Copy assignment is disabled; the context owns its services.
419 : io_context& operator=(io_context const&) = delete;
420 :
421 : /** Return an executor for this context.
422 :
423 : The returned executor can be used to dispatch coroutines
424 : and post work items to this context.
425 :
426 : @return An executor associated with this context.
427 : */
428 : executor_type get_executor() const noexcept;
429 :
430 : /** Signal the context to stop processing.
431 :
432 : This causes `run()` to return as soon as possible. Any pending
433 : work items remain queued.
434 : */
435 63 : void stop()
436 : {
437 63 : sched_->stop();
438 63 : }
439 :
440 : /** Return whether the context stopped.
441 :
442 : @return `true` after a call to `stop()` with no later
443 : call to `restart()`.
444 : */
445 3526 : bool stopped() const noexcept
446 : {
447 3526 : return sched_->stopped();
448 : }
449 :
450 : /** Restart the context after being stopped.
451 :
452 : This function must be called before `run()` can be called
453 : again after a call to `stop()`.
454 : */
455 5750 : void restart()
456 : {
457 5750 : sched_->restart();
458 5750 : }
459 :
460 : /** Process all pending work items.
461 :
462 : This function blocks until it executes all pending work items,
463 : or until `stop()` is called. The context is stopped
464 : when there is no more outstanding work.
465 :
466 : @note The context must be restarted with `restart()` before
467 : calling this function again after it returns.
468 :
469 : @return The number of handlers executed.
470 : */
471 6413 : std::size_t run()
472 : {
473 6413 : frame_allocator_restore restore;
474 12823 : return sched_->run();
475 6413 : }
476 :
477 : /** Process at most one pending work item.
478 :
479 : This function blocks until it executes one work item
480 : or `stop()` is called. The context is stopped when there
481 : is no more outstanding work.
482 :
483 : @note The context must be restarted with `restart()` before
484 : calling this function again after it returns.
485 :
486 : @return The number of handlers executed (0 or 1).
487 : */
488 128 : std::size_t run_one()
489 : {
490 128 : frame_allocator_restore restore;
491 256 : return sched_->run_one();
492 128 : }
493 :
494 : /** Process work items for the specified duration.
495 :
496 : This function blocks until it has executed work items for the
497 : specified duration, or until `stop()` is called. The context
498 : is stopped when there is no more outstanding work.
499 :
500 : @note The context must be restarted with `restart()` before
501 : calling this function again after it returns.
502 :
503 : @param rel_time The duration for which to process work.
504 :
505 : @return The number of handlers executed.
506 : */
507 : template<class Rep, class Period>
508 825 : std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
509 : {
510 825 : return run_until(std::chrono::steady_clock::now() + rel_time);
511 : }
512 :
513 : /** Process work items until the specified time.
514 :
515 : This function blocks until the specified time is reached
516 : or `stop()` is called. The context is stopped when there
517 : is no more outstanding work.
518 :
519 : @note The context must be restarted with `restart()` before
520 : calling this function again after it returns.
521 :
522 : @param abs_time The time point until which to process work.
523 :
524 : @return The number of handlers executed.
525 : */
526 : template<class Clock, class Duration>
527 : std::size_t
528 826 : run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
529 : {
530 826 : std::size_t n = 0;
531 2453 : while (run_one_until(abs_time))
532 1627 : if (n != (std::numeric_limits<std::size_t>::max)())
533 1627 : ++n;
534 826 : return n;
535 : }
536 :
537 : /** Process at most one work item for the specified duration.
538 :
539 : This function blocks until it executes one work item,
540 : the specified duration has elapsed, or `stop()` is called.
541 : The context is stopped when there is no more outstanding work.
542 :
543 : @note The context must be restarted with `restart()` before
544 : calling this function again after it returns.
545 :
546 : @param rel_time The duration for which the call may block.
547 :
548 : @return The number of handlers executed (0 or 1).
549 : */
550 : template<class Rep, class Period>
551 1075 : std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
552 : {
553 1075 : return run_one_until(std::chrono::steady_clock::now() + rel_time);
554 : }
555 :
556 : /** Process at most one work item until the specified time.
557 :
558 : This function blocks until it executes one work item,
559 : the specified time is reached, or `stop()` is called.
560 : The context is stopped when there is no more outstanding work.
561 :
562 : @note The context must be restarted with `restart()` before
563 : calling this function again after it returns.
564 :
565 : @param abs_time The time point until which the call may block.
566 :
567 : @return The number of handlers executed (0 or 1).
568 : */
569 : template<class Clock, class Duration>
570 : std::size_t
571 3536 : run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
572 : {
573 3536 : frame_allocator_restore restore;
574 3536 : typename Clock::time_point now = Clock::now();
575 2604 : for (;;)
576 : {
577 6140 : auto rel_time = abs_time - now;
578 : using rel_type = decltype(rel_time);
579 6140 : if (rel_time < rel_type::zero())
580 5 : rel_time = rel_type::zero();
581 6135 : else if (rel_time > std::chrono::seconds(1))
582 5947 : rel_time = std::chrono::seconds(1);
583 :
584 6140 : std::size_t s = sched_->wait_one(
585 : static_cast<long>(
586 6140 : std::chrono::duration_cast<std::chrono::microseconds>(
587 : rel_time)
588 6140 : .count()));
589 :
590 6140 : if (s || stopped())
591 3500 : return s;
592 :
593 2640 : now = Clock::now();
594 2640 : if (now >= abs_time)
595 36 : return 0;
596 : }
597 3536 : }
598 :
599 : /** Process all ready work items without blocking.
600 :
601 : This function executes all work items that are ready to run
602 : without blocking for more work. The context is stopped
603 : when there is no more outstanding work.
604 :
605 : @note The context must be restarted with `restart()` before
606 : calling this function again after it returns.
607 :
608 : @return The number of handlers executed.
609 : */
610 1323 : std::size_t poll()
611 : {
612 1323 : frame_allocator_restore restore;
613 2646 : return sched_->poll();
614 1323 : }
615 :
616 : /** Process at most one ready work item without blocking.
617 :
618 : This function executes at most one work item that is ready
619 : to run without blocking for more work. The context is
620 : stopped when there is no more outstanding work.
621 :
622 : @note The context must be restarted with `restart()` before
623 : calling this function again after it returns.
624 :
625 : @return The number of handlers executed (0 or 1).
626 : */
627 56 : std::size_t poll_one()
628 : {
629 56 : frame_allocator_restore restore;
630 112 : return sched_->poll_one();
631 56 : }
632 : };
633 :
634 : /** Dispatches and posts work to an I/O context.
635 :
636 : The executor provides the interface for posting work items and
637 : dispatching coroutines to the associated context. It satisfies
638 : the `capy::Executor` concept.
639 :
640 : Executors are lightweight handles that can be copied and compared
641 : for equality. Two executors compare equal if they refer to the
642 : same context.
643 :
644 : @par Thread Safety
645 : Distinct objects: Safe.@n
646 : Shared objects: Safe.
647 : */
648 : class io_context::executor_type
649 : {
650 : io_context* ctx_ = nullptr;
651 :
652 : public:
653 : /** Constructs an executor not associated with any context. */
654 2057 : executor_type() = default;
655 :
656 : /** Construct an executor from a context.
657 :
658 : @param ctx The context to associate with this executor.
659 : */
660 18346 : explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
661 :
662 : /** Return a reference to the associated execution context.
663 :
664 : @return Reference to the context.
665 : */
666 46622 : io_context& context() const noexcept
667 : {
668 46622 : return *ctx_;
669 : }
670 :
671 : /** Check if the current thread is running this executor's context.
672 :
673 : @return `true` if `run()` is being called on this thread.
674 : */
675 515843 : bool running_in_this_thread() const noexcept
676 : {
677 : // The innermost scheduler answers the common case without a
678 : // virtual call or a frame walk.
679 539600 : return detail::running_scheduler.get() == ctx_->sched_ ||
680 539600 : ctx_->sched_->running_in_this_thread();
681 : }
682 :
683 : /** Informs the executor that work is beginning.
684 :
685 : Must be paired with `on_work_finished()`.
686 : */
687 25211 : void on_work_started() const noexcept
688 : {
689 25211 : ctx_->sched_->work_started();
690 25211 : }
691 :
692 : /** Informs the executor that work has completed.
693 :
694 : @pre A preceding call to `on_work_started()` on an equal executor.
695 : */
696 25211 : void on_work_finished() const noexcept
697 : {
698 25211 : ctx_->sched_->work_finished();
699 25211 : }
700 :
701 : /** Dispatch a continuation.
702 :
703 : Returns a handle for symmetric transfer. If called from
704 : within `run()`, returns `c.h`. Otherwise posts `c` for
705 : later execution and returns `std::noop_coroutine()`.
706 :
707 : @param c The continuation to dispatch.
708 :
709 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
710 :
711 : @pre The associated context must outlive this call. Dispatching
712 : concurrently with, or after, the context's destruction is
713 : undefined behavior.
714 : */
715 515836 : std::coroutine_handle<> dispatch(capy::continuation& c) const
716 : {
717 515836 : if (running_in_this_thread())
718 492082 : return c.h;
719 23754 : post(c);
720 23754 : return std::noop_coroutine();
721 : }
722 :
723 : /** Post a continuation for deferred execution.
724 :
725 : Enqueues `c` directly on the scheduler's ready queue.
726 : No heap allocation occurs.
727 :
728 : @param c The continuation to enqueue.
729 :
730 : @pre The associated context must outlive this call. Posting
731 : concurrently with, or after, the context's destruction is
732 : undefined behavior.
733 : */
734 252861 : void post(capy::continuation& c) const
735 : {
736 252861 : ctx_->sched_->post(c);
737 252861 : }
738 :
739 : /** Post a bare coroutine handle for deferred execution.
740 :
741 : Heap-allocates a `scheduler_op` to wrap the handle. A caller
742 : that already owns a `capy::continuation` can post it directly
743 : via the `post(capy::continuation&)` overload to avoid the
744 : allocation.
745 :
746 : A handle still queued when the context is torn down is
747 : destroyed alone, since it carries no owner to unwind its chain
748 : from. Post a coroutine that another frame awaits through the
749 : `post(capy::continuation&)` overload, with its owner set.
750 :
751 : @param h The coroutine handle to post.
752 :
753 : @pre The associated context must outlive this call. Posting
754 : concurrently with, or after, the context's destruction is
755 : undefined behavior.
756 :
757 : @pre Nothing awaits `h`.
758 : */
759 3672 : void post(std::coroutine_handle<> h) const
760 : {
761 3672 : ctx_->sched_->post(h);
762 3672 : }
763 :
764 : /** Compare two executors for equality.
765 :
766 : @return `true` if both executors refer to the same context.
767 : */
768 2 : bool operator==(executor_type const& other) const noexcept
769 : {
770 2 : return ctx_ == other.ctx_;
771 : }
772 :
773 : /** Compare two executors for inequality.
774 :
775 : @return `true` if the executors refer to different contexts.
776 : */
777 : bool operator!=(executor_type const& other) const noexcept
778 : {
779 : return ctx_ != other.ctx_;
780 : }
781 : };
782 :
783 : inline io_context::executor_type
784 18346 : io_context::get_executor() const noexcept
785 : {
786 18346 : return executor_type(const_cast<io_context&>(*this));
787 : }
788 :
789 : } // namespace boost::corosio
790 :
791 : #endif // BOOST_COROSIO_IO_CONTEXT_HPP
|