LCOV - code coverage report
Current view: top level - corosio - io_context.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 98 98
Test Date: 2026-10-28 12:45:41 Functions: 100.0 % 31 31

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

Generated by: LCOV version 2.3