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