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) :191 58x 100.0% 100.0% boost::corosio::io_context::frame_allocator_restore::frame_allocator_restore() :281 11753x 100.0% 100.0% boost::corosio::io_context::frame_allocator_restore::~frame_allocator_restore() :286 11753x 100.0% 100.0% boost::corosio::io_context::io_context<boost::corosio::epoll_t>(boost::corosio::epoll_t, unsigned int) :354 1965x 100.0% 86.0% boost::corosio::io_context::io_context<boost::corosio::select_t>(boost::corosio::select_t, unsigned int) :354 1969x 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) :389 27x 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) :389 26x 100.0% 100.0% boost::corosio::io_context::stop() :454 63x 100.0% 100.0% boost::corosio::io_context::stopped() const :464 3521x 100.0% 100.0% boost::corosio::io_context::restart() :474 5879x 100.0% 100.0% boost::corosio::io_context::run() :496 6540x 100.0% 100.0% boost::corosio::io_context::run_one() :519 126x 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&) :545 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&) :545 808x 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&) :571 828x 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&) :600 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&) :600 978x 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&) :626 3531x 100.0% 78.0% boost::corosio::io_context::poll() :671 1327x 100.0% 78.0% boost::corosio::io_context::poll_one() :694 58x 100.0% 78.0% boost::corosio::io_context::executor_type::executor_type() :721 2057x 100.0% 100.0% boost::corosio::io_context::executor_type::executor_type(boost::corosio::io_context&) :727 18594x 100.0% 100.0% boost::corosio::io_context::executor_type::context() const :733 45798x 100.0% 100.0% boost::corosio::io_context::executor_type::running_in_this_thread() const :742 504833x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_started() const :754 25421x 100.0% 100.0% boost::corosio::io_context::executor_type::on_work_finished() const :763 25421x 100.0% 100.0% boost::corosio::io_context::executor_type::dispatch(boost::capy::continuation&) const :782 504826x 100.0% 100.0% boost::corosio::io_context::executor_type::post(boost::capy::continuation&) const :801 236716x 100.0% 100.0% boost::corosio::io_context::executor_type::post(std::__n4861::coroutine_handle<void>) const :826 3672x 100.0% 100.0% boost::corosio::io_context::executor_type::operator==(boost::corosio::io_context::executor_type const&) const :835 2x 100.0% 100.0% boost::corosio::io_context::get_executor() const :851 18594x 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 @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 58x effective_concurrency_hint(
192 io_context_options const& opts, unsigned hint) noexcept
193 {
194 58x 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 11753x 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 11753x ~frame_allocator_restore()
287 {
288 11753x capy::set_current_frame_allocator(saved);
289 11753x }
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 3934x explicit io_context(
355 [[maybe_unused]] Backend backend,
356 unsigned concurrency_hint = std::thread::hardware_concurrency())
357 : capy::execution_context(this)
358 3934x , sched_(nullptr)
359 {
360 3934x 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 3918x apply_threading_(io_context_options{});
364 3934x }
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 53x 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 53x , sched_(nullptr)
395 {
396 53x apply_options_pre_(opts);
397 // Effective hint (1 for lockless tiers); see effective_concurrency_hint.
398 unsigned const eff =
399 41x detail::effective_concurrency_hint(opts, concurrency_hint);
400 41x sched_ = &Backend::construct(*this, eff);
401 41x apply_options_post_(opts, eff);
402 53x }
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 63x void stop()
455 {
456 63x sched_->stop();
457 63x }
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 3521x bool stopped() const noexcept
465 {
466 3521x 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 5879x void restart()
475 {
476 5879x sched_->restart();
477 5879x }
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 6540x std::size_t run()
497 {
498 6540x frame_allocator_restore restore;
499 13077x return sched_->run();
500 6540x }
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 126x std::size_t run_one()
520 {
521 126x frame_allocator_restore restore;
522 252x return sched_->run_one();
523 126x }
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 827x std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
546 {
547 827x 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 828x run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
572 {
573 828x std::size_t n = 0;
574 2450x while (run_one_until(abs_time))
575 1622x if (n != (std::numeric_limits<std::size_t>::max)())
576 1622x ++n;
577 828x 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 1073x std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
601 {
602 1073x 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 3531x run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
627 {
628 3531x frame_allocator_restore restore;
629 3531x typename Clock::time_point now = Clock::now();
630 2597x for (;;)
631 {
632 6128x auto rel_time = abs_time - now;
633 using rel_type = decltype(rel_time);
634 6128x if (rel_time < rel_type::zero())
635 5x rel_time = rel_type::zero();
636 6123x else if (rel_time > std::chrono::seconds(1))
637 5934x rel_time = std::chrono::seconds(1);
638
639 6128x std::size_t s = sched_->wait_one(
640 static_cast<long>(
641 6128x std::chrono::duration_cast<std::chrono::microseconds>(
642 rel_time)
643 6128x .count()));
644
645 6128x if (s || stopped())
646 3495x return s;
647
648 2633x now = Clock::now();
649 2633x if (now >= abs_time)
650 36x return 0;
651 }
652 3531x }
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 1327x std::size_t poll()
672 {
673 1327x frame_allocator_restore restore;
674 2654x return sched_->poll();
675 1327x }
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 58x std::size_t poll_one()
695 {
696 58x frame_allocator_restore restore;
697 116x return sched_->poll_one();
698 58x }
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 2057x executor_type() = default;
722
723 /** Construct an executor from a context.
724
725 @param ctx The context to associate with this executor.
726 */
727 18594x 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 45798x io_context& context() const noexcept
734 {
735 45798x 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 504833x 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 528789x return detail::running_scheduler.get() == ctx_->sched_ ||
747 528789x 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 25421x void on_work_started() const noexcept
755 {
756 25421x ctx_->sched_->work_started();
757 25421x }
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 25421x void on_work_finished() const noexcept
764 {
765 25421x ctx_->sched_->work_finished();
766 25421x }
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 504826x std::coroutine_handle<> dispatch(capy::continuation& c) const
783 {
784 504826x if (running_in_this_thread())
785 480873x return c.h;
786 23953x post(c);
787 23953x 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 236716x void post(capy::continuation& c) const
802 {
803 236716x ctx_->sched_->post(c);
804 236716x }
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 3672x void post(std::coroutine_handle<> h) const
827 {
828 3672x ctx_->sched_->post(h);
829 3672x }
830
831 /** Compare two executors for equality.
832
833 @return `true` if both executors refer to the same context.
834 */
835 2x bool operator==(executor_type const& other) const noexcept
836 {
837 2x 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 18594x io_context::get_executor() const noexcept
852 {
853 18594x return executor_type(const_cast<io_context&>(*this));
854 }
855
856 } // namespace boost::corosio
857
858 #endif // BOOST_COROSIO_IO_CONTEXT_HPP
859