include/boost/corosio/io_context.hpp

100.0% Lines (98 / 98) 100.0% Functions (31 / 31)
io_context.hpp
f(x) Functions (31)
Function Calls Lines Blocks
boost::corosio::detail::effective_concurrency_hint(boost::corosio::io_context_options const&, unsigned int) :182 62x 100.0% 100.0% boost::corosio::io_context::frame_allocator_restore::frame_allocator_restore() :272 11607x 100.0% 100.0% boost::corosio::io_context::frame_allocator_restore::~frame_allocator_restore() :277 11607x 100.0% 100.0% boost::corosio::io_context::io_context<boost::corosio::epoll_t>(boost::corosio::epoll_t, unsigned int) :343 1909x 100.0% 86.0% boost::corosio::io_context::io_context<boost::corosio::select_t>(boost::corosio::select_t, unsigned int) :343 1930x 100.0% 86.0% boost::corosio::io_context::io_context<boost::corosio::epoll_t>(boost::corosio::epoll_t, boost::corosio::io_context_options const&, unsigned int) :376 23x 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) :376 22x 100.0% 100.0% boost::corosio::io_context::stop() :435 63x 100.0% 100.0% boost::corosio::io_context::stopped() const :445 3526x 100.0% 100.0% boost::corosio::io_context::restart() :455 5750x 100.0% 100.0% boost::corosio::io_context::run() :471 6413x 100.0% 100.0% boost::corosio::io_context::run_one() :488 128x 100.0% 78.0% unsigned long boost::corosio::io_context::run_for<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) :508 19x 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&) :508 806x 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&) :528 826x 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&) :551 95x 100.0% 88.0% unsigned long boost::corosio::io_context::run_one_for<long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> > const&) :551 980x 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&) :571 3536x 100.0% 78.0% boost::corosio::io_context::poll() :610 1323x 100.0% 78.0% boost::corosio::io_context::poll_one() :627 56x 100.0% 78.0% boost::corosio::io_context::executor_type::executor_type() :654 2057x 100.0% 100.0% boost::corosio::io_context::executor_type::executor_type(boost::corosio::io_context&) :660 18346x 100.0% 100.0% boost::corosio::io_context::executor_type::context() const :666 46622x 100.0% 100.0% boost::corosio::io_context::executor_type::running_in_this_thread() const :675 515843x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_started() const :687 25211x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_finished() const :696 25211x 100.0% 100.0% boost::corosio::io_context::executor_type::dispatch(boost::capy::continuation&) const :715 515836x 100.0% 100.0% boost::corosio::io_context::executor_type::post(boost::capy::continuation&) const :734 252861x 100.0% 100.0% boost::corosio::io_context::executor_type::post(std::__n4861::coroutine_handle<void>) const :759 3672x 100.0% 100.0% boost::corosio::io_context::executor_type::operator==(boost::corosio::io_context::executor_type const&) const :768 2x 100.0% 100.0% boost::corosio::io_context::get_executor() const :784 18346x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Steve Gerbino
4 // Copyright (c) 2026 Michael Vandeberg
5 //
6 // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // Official repository: https://github.com/cppalliance/corosio
10 //
11
12 #ifndef BOOST_COROSIO_IO_CONTEXT_HPP
13 #define BOOST_COROSIO_IO_CONTEXT_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/detail/platform.hpp>
17 #include <boost/corosio/detail/scheduler.hpp>
18 #include <boost/capy/continuation.hpp>
19 #include <boost/capy/ex/execution_context.hpp>
20 #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 62x effective_concurrency_hint(
183 io_context_options const& opts, unsigned hint) noexcept
184 {
185 62x 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 11607x 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 11607x ~frame_allocator_restore()
278 {
279 11607x capy::set_current_frame_allocator(saved);
280 11607x }
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 3839x explicit io_context(
344 [[maybe_unused]] Backend backend,
345 unsigned concurrency_hint = std::thread::hardware_concurrency())
346 : capy::execution_context(this)
347 3839x , sched_(nullptr)
348 {
349 3839x 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 3827x apply_threading_(io_context_options{});
353 3839x }
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 45x 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 45x , sched_(nullptr)
382 {
383 45x apply_options_pre_(opts);
384 // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
385 unsigned const eff =
386 45x detail::effective_concurrency_hint(opts, concurrency_hint);
387 45x sched_ = &Backend::construct(*this, eff);
388 45x apply_options_post_(opts, eff);
389 45x }
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 63x void stop()
436 {
437 63x sched_->stop();
438 63x }
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 3526x bool stopped() const noexcept
446 {
447 3526x 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 5750x void restart()
456 {
457 5750x sched_->restart();
458 5750x }
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 6413x std::size_t run()
472 {
473 6413x frame_allocator_restore restore;
474 12823x return sched_->run();
475 6413x }
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 128x std::size_t run_one()
489 {
490 128x frame_allocator_restore restore;
491 256x return sched_->run_one();
492 128x }
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 825x std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
509 {
510 825x 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 826x run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
529 {
530 826x std::size_t n = 0;
531 2453x while (run_one_until(abs_time))
532 1627x if (n != (std::numeric_limits<std::size_t>::max)())
533 1627x ++n;
534 826x 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 1075x std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
552 {
553 1075x 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 3536x run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
572 {
573 3536x frame_allocator_restore restore;
574 3536x typename Clock::time_point now = Clock::now();
575 2604x for (;;)
576 {
577 6140x auto rel_time = abs_time - now;
578 using rel_type = decltype(rel_time);
579 6140x if (rel_time < rel_type::zero())
580 5x rel_time = rel_type::zero();
581 6135x else if (rel_time > std::chrono::seconds(1))
582 5947x rel_time = std::chrono::seconds(1);
583
584 6140x std::size_t s = sched_->wait_one(
585 static_cast<long>(
586 6140x std::chrono::duration_cast<std::chrono::microseconds>(
587 rel_time)
588 6140x .count()));
589
590 6140x if (s || stopped())
591 3500x return s;
592
593 2640x now = Clock::now();
594 2640x if (now >= abs_time)
595 36x return 0;
596 }
597 3536x }
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 1323x std::size_t poll()
611 {
612 1323x frame_allocator_restore restore;
613 2646x return sched_->poll();
614 1323x }
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 56x std::size_t poll_one()
628 {
629 56x frame_allocator_restore restore;
630 112x return sched_->poll_one();
631 56x }
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 2057x executor_type() = default;
655
656 /** Construct an executor from a context.
657
658 @param ctx The context to associate with this executor.
659 */
660 18346x 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 46622x io_context& context() const noexcept
667 {
668 46622x 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 515843x 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 539600x return detail::running_scheduler.get() == ctx_->sched_ ||
680 539600x 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 25211x void on_work_started() const noexcept
688 {
689 25211x ctx_->sched_->work_started();
690 25211x }
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 25211x void on_work_finished() const noexcept
697 {
698 25211x ctx_->sched_->work_finished();
699 25211x }
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 515836x std::coroutine_handle<> dispatch(capy::continuation& c) const
716 {
717 515836x if (running_in_this_thread())
718 492082x return c.h;
719 23754x post(c);
720 23754x 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 252861x void post(capy::continuation& c) const
735 {
736 252861x ctx_->sched_->post(c);
737 252861x }
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 3672x void post(std::coroutine_handle<> h) const
760 {
761 3672x ctx_->sched_->post(h);
762 3672x }
763
764 /** Compare two executors for equality.
765
766 @return `true` if both executors refer to the same context.
767 */
768 2x bool operator==(executor_type const& other) const noexcept
769 {
770 2x 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 18346x io_context::get_executor() const noexcept
785 {
786 18346x return executor_type(const_cast<io_context&>(*this));
787 }
788
789 } // namespace boost::corosio
790
791 #endif // BOOST_COROSIO_IO_CONTEXT_HPP
792