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 11490x 100.0% 100.0% boost::corosio::io_context::frame_allocator_restore::~frame_allocator_restore() :277 11490x 100.0% 100.0% boost::corosio::io_context::io_context<boost::corosio::epoll_t>(boost::corosio::epoll_t, unsigned int) :343 1910x 100.0% 86.0% boost::corosio::io_context::io_context<boost::corosio::select_t>(boost::corosio::select_t, unsigned int) :343 1931x 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() :437 63x 100.0% 100.0% boost::corosio::io_context::stopped() const :447 3527x 100.0% 100.0% boost::corosio::io_context::restart() :457 5616x 100.0% 100.0% boost::corosio::io_context::run() :473 6292x 100.0% 100.0% boost::corosio::io_context::run_one() :490 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&) :510 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&) :510 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&) :530 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&) :553 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&) :553 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&) :573 3538x 100.0% 78.0% boost::corosio::io_context::poll() :612 1325x 100.0% 78.0% boost::corosio::io_context::poll_one() :629 56x 100.0% 78.0% boost::corosio::io_context::executor_type::executor_type() :656 2057x 100.0% 100.0% boost::corosio::io_context::executor_type::executor_type(boost::corosio::io_context&) :662 18077x 100.0% 100.0% boost::corosio::io_context::executor_type::context() const :668 46105x 100.0% 100.0% boost::corosio::io_context::executor_type::running_in_this_thread() const :677 505806x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_started() const :689 24959x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_finished() const :698 24959x 100.0% 100.0% boost::corosio::io_context::executor_type::dispatch(boost::capy::continuation&) const :717 505799x 100.0% 100.0% boost::corosio::io_context::executor_type::post(boost::capy::continuation&) const :736 252232x 100.0% 100.0% boost::corosio::io_context::executor_type::post(std::__n4861::coroutine_handle<void>) const :761 3672x 100.0% 100.0% boost::corosio::io_context::executor_type::operator==(boost::corosio::io_context::executor_type const&) const :770 2x 100.0% 100.0% boost::corosio::io_context::get_executor() const :786 18077x 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 11490x 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 11490x ~frame_allocator_restore()
278 {
279 11490x capy::set_current_frame_allocator(saved);
280 11490x }
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 3841x explicit io_context(
344 [[maybe_unused]] Backend backend,
345 unsigned concurrency_hint = std::thread::hardware_concurrency())
346 : capy::execution_context(this)
347 3841x , sched_(nullptr)
348 {
349 3841x 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 3829x apply_threading_(io_context_options{});
353 3841x }
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 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 63x void stop()
438 {
439 63x sched_->stop();
440 63x }
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 3527x bool stopped() const noexcept
448 {
449 3527x 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 5616x void restart()
458 {
459 5616x sched_->restart();
460 5616x }
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 6292x std::size_t run()
474 {
475 6292x frame_allocator_restore restore;
476 12581x return sched_->run();
477 6292x }
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 128x std::size_t run_one()
491 {
492 128x frame_allocator_restore restore;
493 256x return sched_->run_one();
494 128x }
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 825x std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
511 {
512 825x 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 826x run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
531 {
532 826x std::size_t n = 0;
533 2455x while (run_one_until(abs_time))
534 1629x if (n != (std::numeric_limits<std::size_t>::max)())
535 1629x ++n;
536 826x 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 1075x std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
554 {
555 1075x 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 3538x run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
574 {
575 3538x frame_allocator_restore restore;
576 3538x typename Clock::time_point now = Clock::now();
577 2605x for (;;)
578 {
579 6143x auto rel_time = abs_time - now;
580 using rel_type = decltype(rel_time);
581 6143x if (rel_time < rel_type::zero())
582 5x rel_time = rel_type::zero();
583 6138x else if (rel_time > std::chrono::seconds(1))
584 5949x rel_time = std::chrono::seconds(1);
585
586 6143x std::size_t s = sched_->wait_one(
587 static_cast<long>(
588 6143x std::chrono::duration_cast<std::chrono::microseconds>(
589 rel_time)
590 6143x .count()));
591
592 6143x if (s || stopped())
593 3502x return s;
594
595 2641x now = Clock::now();
596 2641x if (now >= abs_time)
597 36x return 0;
598 }
599 3538x }
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 1325x std::size_t poll()
613 {
614 1325x frame_allocator_restore restore;
615 2650x return sched_->poll();
616 1325x }
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 56x std::size_t poll_one()
630 {
631 56x frame_allocator_restore restore;
632 112x return sched_->poll_one();
633 56x }
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 2057x executor_type() = default;
657
658 /** Construct an executor from a context.
659
660 @param ctx The context to associate with this executor.
661 */
662 18077x 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 46105x io_context& context() const noexcept
669 {
670 46105x 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 505806x 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 529462x return detail::running_scheduler.get() == ctx_->sched_ ||
682 529462x 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 24959x void on_work_started() const noexcept
690 {
691 24959x ctx_->sched_->work_started();
692 24959x }
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 24959x void on_work_finished() const noexcept
699 {
700 24959x ctx_->sched_->work_finished();
701 24959x }
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 505799x std::coroutine_handle<> dispatch(capy::continuation& c) const
718 {
719 505799x if (running_in_this_thread())
720 482146x return c.h;
721 23653x post(c);
722 23653x 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 252232x void post(capy::continuation& c) const
737 {
738 252232x ctx_->sched_->post(c);
739 252232x }
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 3672x void post(std::coroutine_handle<> h) const
762 {
763 3672x ctx_->sched_->post(h);
764 3672x }
765
766 /** Compare two executors for equality.
767
768 @return `true` if both executors refer to the same context.
769 */
770 2x bool operator==(executor_type const& other) const noexcept
771 {
772 2x 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 18077x io_context::get_executor() const noexcept
787 {
788 18077x return executor_type(const_cast<io_context&>(*this));
789 }
790
791 } // namespace boost::corosio
792
793 #endif // BOOST_COROSIO_IO_CONTEXT_HPP
794