LCOV - code coverage report
Current view: top level - corosio - io_context.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 97.3 % 74 72 2
Test Date: 2026-05-06 00:06:53 Functions: 100.0 % 23 23

           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/continuation_op.hpp>
      17                 : #include <boost/corosio/detail/platform.hpp>
      18                 : #include <boost/corosio/detail/scheduler.hpp>
      19                 : #include <boost/capy/continuation.hpp>
      20                 : #include <boost/capy/ex/execution_context.hpp>
      21                 : 
      22                 : #include <chrono>
      23                 : #include <coroutine>
      24                 : #include <cstddef>
      25                 : #include <limits>
      26                 : #include <thread>
      27                 : 
      28                 : namespace boost::corosio {
      29                 : 
      30                 : /** Runtime tuning options for @ref io_context.
      31                 : 
      32                 :     All fields have defaults that match the library's built-in
      33                 :     values, so constructing a default `io_context_options` produces
      34                 :     identical behavior to an unconfigured context.
      35                 : 
      36                 :     Options that apply only to a specific backend family are
      37                 :     silently ignored when the active backend does not support them.
      38                 : 
      39                 :     @par Example
      40                 :     @code
      41                 :     io_context_options opts;
      42                 :     opts.max_events_per_poll  = 256;   // larger batch per syscall
      43                 :     opts.inline_budget_max    = 32;    // more speculative completions
      44                 :     opts.thread_pool_size     = 4;     // more file-I/O workers
      45                 : 
      46                 :     io_context ioc(opts);
      47                 :     @endcode
      48                 : 
      49                 :     @see io_context, native_io_context
      50                 : */
      51                 : struct io_context_options
      52                 : {
      53                 :     /** Maximum events fetched per reactor poll call.
      54                 : 
      55                 :         Controls the buffer size passed to `epoll_wait()` or
      56                 :         `kevent()`. Larger values reduce syscall frequency under
      57                 :         high load; smaller values improve fairness between
      58                 :         connections. Ignored on IOCP and select backends.
      59                 :     */
      60                 :     unsigned max_events_per_poll = 128;
      61                 : 
      62                 :     /** Starting inline completion budget per handler chain.
      63                 : 
      64                 :         After a posted handler executes, the reactor grants this
      65                 :         many speculative inline completions before forcing a
      66                 :         re-queue. Applies to reactor backends only.
      67                 : 
      68                 :         @note Constructing an `io_context` with `concurrency_hint > 1`
      69                 :             and all three budget fields at their defaults overrides
      70                 :             them to disable inline completion (post-everything mode),
      71                 :             since multi-thread workloads benefit from cross-thread
      72                 :             work-stealing. Setting any budget field to a non-default
      73                 :             value disables the override.
      74                 :     */
      75                 :     unsigned inline_budget_initial = 2;
      76                 : 
      77                 :     /** Hard ceiling on adaptive inline budget ramp-up.
      78                 : 
      79                 :         The budget doubles each cycle it is fully consumed, up to
      80                 :         this limit. Applies to reactor backends only.
      81                 :     */
      82                 :     unsigned inline_budget_max = 16;
      83                 : 
      84                 :     /** Inline budget when no other thread assists the reactor.
      85                 : 
      86                 :         When only one thread is running the event loop, this
      87                 :         value caps the inline budget to preserve fairness.
      88                 :         Applies to reactor backends only.
      89                 :     */
      90                 :     unsigned unassisted_budget = 4;
      91                 : 
      92                 :     /** Maximum `GetQueuedCompletionStatus` timeout in milliseconds.
      93                 : 
      94                 :         Bounds how long the IOCP scheduler blocks between timer
      95                 :         rechecks. Lower values improve timer responsiveness at the
      96                 :         cost of more syscalls. Applies to IOCP only.
      97                 :     */
      98                 :     unsigned gqcs_timeout_ms = 500;
      99                 : 
     100                 :     /** Thread pool size for blocking I/O (file I/O, DNS resolution).
     101                 : 
     102                 :         Sets the number of worker threads in the shared thread pool
     103                 :         used by POSIX file services and DNS resolution. Must be at
     104                 :         least 1. Applies to POSIX backends only; ignored on IOCP
     105                 :         where file I/O uses native overlapped I/O.
     106                 :     */
     107                 :     unsigned thread_pool_size = 1;
     108                 : 
     109                 :     /** Enable single-threaded mode (disable scheduler locking).
     110                 : 
     111                 :         When true, the scheduler skips all mutex lock/unlock and
     112                 :         condition variable operations on the hot path. This
     113                 :         eliminates synchronization overhead when only one thread
     114                 :         calls `run()`.
     115                 : 
     116                 :         @par Restrictions
     117                 :         - Only one thread may call `run()` (or any run variant).
     118                 :         - Posting work from another thread is undefined behavior.
     119                 :         - DNS resolution returns `operation_not_supported`.
     120                 :         - POSIX file I/O returns `operation_not_supported`.
     121                 :         - Signal sets should not be shared across contexts.
     122                 : 
     123                 :         @note Constructing an `io_context` with `concurrency_hint == 1`
     124                 :             automatically enables single-threaded mode regardless of
     125                 :             this field's value, matching asio's convention. To opt out,
     126                 :             pass `concurrency_hint > 1`.
     127                 :     */
     128                 :     bool single_threaded = false;
     129                 : };
     130                 : 
     131                 : namespace detail {
     132                 : class timer_service;
     133                 : struct timer_service_access;
     134                 : } // namespace detail
     135                 : 
     136                 : /** An I/O context for running asynchronous operations.
     137                 : 
     138                 :     The io_context provides an execution environment for async
     139                 :     operations. It maintains a queue of pending work items and
     140                 :     processes them when `run()` is called.
     141                 : 
     142                 :     The default and unsigned constructors select the platform's
     143                 :     native backend:
     144                 :     - Windows: IOCP
     145                 :     - Linux: epoll
     146                 :     - BSD/macOS: kqueue
     147                 :     - Other POSIX: select
     148                 : 
     149                 :     The template constructor accepts a backend tag value to
     150                 :     choose a specific backend at compile time:
     151                 : 
     152                 :     @par Example
     153                 :     @code
     154                 :     io_context ioc;                   // platform default
     155                 :     io_context ioc2(corosio::epoll);  // explicit backend
     156                 :     @endcode
     157                 : 
     158                 :     @par Thread Safety
     159                 :     Distinct objects: Safe.@n
     160                 :     Shared objects: Safe, if using a concurrency hint greater
     161                 :     than 1.
     162                 : 
     163                 :     @see epoll_t, select_t, kqueue_t, iocp_t
     164                 : */
     165                 : class BOOST_COROSIO_DECL io_context : public capy::execution_context
     166                 : {
     167                 :     friend struct detail::timer_service_access;
     168                 : 
     169                 :     /// Pre-create services that depend on options (before construct).
     170                 :     void apply_options_pre_(io_context_options const& opts);
     171                 : 
     172                 :     /// Apply runtime tuning to the scheduler (after construct).
     173                 :     void apply_options_post_(
     174                 :         io_context_options const& opts,
     175                 :         unsigned concurrency_hint);
     176                 : 
     177                 :     /// Switch the scheduler to single-threaded (lockless) mode.
     178                 :     void configure_single_threaded_();
     179                 : 
     180                 : protected:
     181                 :     detail::timer_service* timer_svc_ = nullptr;
     182                 :     detail::scheduler* sched_;
     183                 : 
     184                 : public:
     185                 :     /** The executor type for this context. */
     186                 :     class executor_type;
     187                 : 
     188                 :     /** Construct with default concurrency and platform backend.
     189                 : 
     190                 :         Uses `std::thread::hardware_concurrency()` clamped to a minimum
     191                 :         of 2 as the concurrency hint, so the default constructor never
     192                 :         silently engages single-threaded mode (see
     193                 :         @ref io_context_options::single_threaded). Pass an explicit
     194                 :         `concurrency_hint == 1` to opt into single-threaded mode.
     195                 :     */
     196                 :     io_context();
     197                 : 
     198                 :     /** Construct with a concurrency hint and platform backend.
     199                 : 
     200                 :         @param concurrency_hint Hint for the number of threads
     201                 :             that will call `run()`.
     202                 :     */
     203                 :     explicit io_context(unsigned concurrency_hint);
     204                 : 
     205                 :     /** Construct with runtime tuning options and platform backend.
     206                 : 
     207                 :         @param opts Runtime options controlling scheduler and
     208                 :             service behavior.
     209                 :         @param concurrency_hint Hint for the number of threads
     210                 :             that will call `run()`.
     211                 :     */
     212                 :     explicit io_context(
     213                 :         io_context_options const& opts,
     214                 :         unsigned concurrency_hint = std::thread::hardware_concurrency());
     215                 : 
     216                 :     /** Construct with an explicit backend tag.
     217                 : 
     218                 :         @param backend The backend tag value selecting the I/O
     219                 :             multiplexer (e.g. `corosio::epoll`).
     220                 :         @param concurrency_hint Hint for the number of threads
     221                 :             that will call `run()`.
     222                 :     */
     223                 :     template<class Backend>
     224                 :         requires requires { Backend::construct; }
     225 HIT         478 :     explicit io_context(
     226                 :         Backend backend,
     227                 :         unsigned concurrency_hint = std::thread::hardware_concurrency())
     228                 :         : capy::execution_context(this)
     229             478 :         , sched_(nullptr)
     230                 :     {
     231                 :         (void)backend;
     232             478 :         sched_ = &Backend::construct(*this, concurrency_hint);
     233             478 :         if (concurrency_hint == 1)
     234 MIS           0 :             configure_single_threaded_();
     235 HIT         478 :     }
     236                 : 
     237                 :     /** Construct with an explicit backend tag and runtime options.
     238                 : 
     239                 :         @param backend The backend tag value selecting the I/O
     240                 :             multiplexer (e.g. `corosio::epoll`).
     241                 :         @param opts Runtime options controlling scheduler and
     242                 :             service behavior.
     243                 :         @param concurrency_hint Hint for the number of threads
     244                 :             that will call `run()`.
     245                 :     */
     246                 :     template<class Backend>
     247                 :         requires requires { Backend::construct; }
     248                 :     explicit io_context(
     249                 :         Backend backend,
     250                 :         io_context_options const& opts,
     251                 :         unsigned concurrency_hint = std::thread::hardware_concurrency())
     252                 :         : capy::execution_context(this)
     253                 :         , sched_(nullptr)
     254                 :     {
     255                 :         (void)backend;
     256                 :         apply_options_pre_(opts);
     257                 :         sched_ = &Backend::construct(*this, concurrency_hint);
     258                 :         apply_options_post_(opts, concurrency_hint);
     259                 :     }
     260                 : 
     261                 :     ~io_context();
     262                 : 
     263                 :     io_context(io_context const&)            = delete;
     264                 :     io_context& operator=(io_context const&) = delete;
     265                 : 
     266                 :     /** Return an executor for this context.
     267                 : 
     268                 :         The returned executor can be used to dispatch coroutines
     269                 :         and post work items to this context.
     270                 : 
     271                 :         @return An executor associated with this context.
     272                 :     */
     273                 :     executor_type get_executor() const noexcept;
     274                 : 
     275                 :     /** Signal the context to stop processing.
     276                 : 
     277                 :         This causes `run()` to return as soon as possible. Any pending
     278                 :         work items remain queued.
     279                 :     */
     280               5 :     void stop()
     281                 :     {
     282               5 :         sched_->stop();
     283               5 :     }
     284                 : 
     285                 :     /** Return whether the context has been stopped.
     286                 : 
     287                 :         @return `true` if `stop()` has been called and `restart()`
     288                 :             has not been called since.
     289                 :     */
     290              62 :     bool stopped() const noexcept
     291                 :     {
     292              62 :         return sched_->stopped();
     293                 :     }
     294                 : 
     295                 :     /** Restart the context after being stopped.
     296                 : 
     297                 :         This function must be called before `run()` can be called
     298                 :         again after `stop()` has been called.
     299                 :     */
     300             111 :     void restart()
     301                 :     {
     302             111 :         sched_->restart();
     303             111 :     }
     304                 : 
     305                 :     /** Process all pending work items.
     306                 : 
     307                 :         This function blocks until all pending work items have been
     308                 :         executed or `stop()` is called. The context is stopped
     309                 :         when there is no more outstanding work.
     310                 : 
     311                 :         @note The context must be restarted with `restart()` before
     312                 :             calling this function again after it returns.
     313                 : 
     314                 :         @return The number of handlers executed.
     315                 :     */
     316             432 :     std::size_t run()
     317                 :     {
     318             432 :         return sched_->run();
     319                 :     }
     320                 : 
     321                 :     /** Process at most one pending work item.
     322                 : 
     323                 :         This function blocks until one work item has been executed
     324                 :         or `stop()` is called. The context is stopped when there
     325                 :         is no more outstanding work.
     326                 : 
     327                 :         @note The context must be restarted with `restart()` before
     328                 :             calling this function again after it returns.
     329                 : 
     330                 :         @return The number of handlers executed (0 or 1).
     331                 :     */
     332               2 :     std::size_t run_one()
     333                 :     {
     334               2 :         return sched_->run_one();
     335                 :     }
     336                 : 
     337                 :     /** Process work items for the specified duration.
     338                 : 
     339                 :         This function blocks until work items have been executed for
     340                 :         the specified duration, or `stop()` is called. The context
     341                 :         is stopped when there is no more outstanding work.
     342                 : 
     343                 :         @note The context must be restarted with `restart()` before
     344                 :             calling this function again after it returns.
     345                 : 
     346                 :         @param rel_time The duration for which to process work.
     347                 : 
     348                 :         @return The number of handlers executed.
     349                 :     */
     350                 :     template<class Rep, class Period>
     351               9 :     std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
     352                 :     {
     353               9 :         return run_until(std::chrono::steady_clock::now() + rel_time);
     354                 :     }
     355                 : 
     356                 :     /** Process work items until the specified time.
     357                 : 
     358                 :         This function blocks until the specified time is reached
     359                 :         or `stop()` is called. The context is stopped when there
     360                 :         is no more outstanding work.
     361                 : 
     362                 :         @note The context must be restarted with `restart()` before
     363                 :             calling this function again after it returns.
     364                 : 
     365                 :         @param abs_time The time point until which to process work.
     366                 : 
     367                 :         @return The number of handlers executed.
     368                 :     */
     369                 :     template<class Clock, class Duration>
     370                 :     std::size_t
     371               9 :     run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
     372                 :     {
     373               9 :         std::size_t n = 0;
     374              58 :         while (run_one_until(abs_time))
     375              49 :             if (n != (std::numeric_limits<std::size_t>::max)())
     376              49 :                 ++n;
     377               9 :         return n;
     378                 :     }
     379                 : 
     380                 :     /** Process at most one work item for the specified duration.
     381                 : 
     382                 :         This function blocks until one work item has been executed,
     383                 :         the specified duration has elapsed, or `stop()` is called.
     384                 :         The context is stopped when there is no more outstanding work.
     385                 : 
     386                 :         @note The context must be restarted with `restart()` before
     387                 :             calling this function again after it returns.
     388                 : 
     389                 :         @param rel_time The duration for which the call may block.
     390                 : 
     391                 :         @return The number of handlers executed (0 or 1).
     392                 :     */
     393                 :     template<class Rep, class Period>
     394               3 :     std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
     395                 :     {
     396               3 :         return run_one_until(std::chrono::steady_clock::now() + rel_time);
     397                 :     }
     398                 : 
     399                 :     /** Process at most one work item until the specified time.
     400                 : 
     401                 :         This function blocks until one work item has been executed,
     402                 :         the specified time is reached, or `stop()` is called.
     403                 :         The context is stopped when there is no more outstanding work.
     404                 : 
     405                 :         @note The context must be restarted with `restart()` before
     406                 :             calling this function again after it returns.
     407                 : 
     408                 :         @param abs_time The time point until which the call may block.
     409                 : 
     410                 :         @return The number of handlers executed (0 or 1).
     411                 :     */
     412                 :     template<class Clock, class Duration>
     413                 :     std::size_t
     414              63 :     run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
     415                 :     {
     416              63 :         typename Clock::time_point now = Clock::now();
     417             104 :         while (now < abs_time)
     418                 :         {
     419             102 :             auto rel_time = abs_time - now;
     420             102 :             if (rel_time > std::chrono::seconds(1))
     421 MIS           0 :                 rel_time = std::chrono::seconds(1);
     422                 : 
     423 HIT         102 :             std::size_t s = sched_->wait_one(
     424                 :                 static_cast<long>(
     425             102 :                     std::chrono::duration_cast<std::chrono::microseconds>(
     426                 :                         rel_time)
     427             102 :                         .count()));
     428                 : 
     429             102 :             if (s || stopped())
     430              61 :                 return s;
     431                 : 
     432              41 :             now = Clock::now();
     433                 :         }
     434               2 :         return 0;
     435                 :     }
     436                 : 
     437                 :     /** Process all ready work items without blocking.
     438                 : 
     439                 :         This function executes all work items that are ready to run
     440                 :         without blocking for more work. The context is stopped
     441                 :         when there is no more outstanding work.
     442                 : 
     443                 :         @note The context must be restarted with `restart()` before
     444                 :             calling this function again after it returns.
     445                 : 
     446                 :         @return The number of handlers executed.
     447                 :     */
     448               6 :     std::size_t poll()
     449                 :     {
     450               6 :         return sched_->poll();
     451                 :     }
     452                 : 
     453                 :     /** Process at most one ready work item without blocking.
     454                 : 
     455                 :         This function executes at most one work item that is ready
     456                 :         to run without blocking for more work. The context is
     457                 :         stopped when there is no more outstanding work.
     458                 : 
     459                 :         @note The context must be restarted with `restart()` before
     460                 :             calling this function again after it returns.
     461                 : 
     462                 :         @return The number of handlers executed (0 or 1).
     463                 :     */
     464               4 :     std::size_t poll_one()
     465                 :     {
     466               4 :         return sched_->poll_one();
     467                 :     }
     468                 : };
     469                 : 
     470                 : /** An executor for dispatching work to an I/O context.
     471                 : 
     472                 :     The executor provides the interface for posting work items and
     473                 :     dispatching coroutines to the associated context. It satisfies
     474                 :     the `capy::Executor` concept.
     475                 : 
     476                 :     Executors are lightweight handles that can be copied and compared
     477                 :     for equality. Two executors compare equal if they refer to the
     478                 :     same context.
     479                 : 
     480                 :     @par Thread Safety
     481                 :     Distinct objects: Safe.@n
     482                 :     Shared objects: Safe.
     483                 : */
     484                 : class io_context::executor_type
     485                 : {
     486                 :     io_context* ctx_ = nullptr;
     487                 : 
     488                 : public:
     489                 :     /** Default constructor.
     490                 : 
     491                 :         Constructs an executor not associated with any context.
     492                 :     */
     493                 :     executor_type() = default;
     494                 : 
     495                 :     /** Construct an executor from a context.
     496                 : 
     497                 :         @param ctx The context to associate with this executor.
     498                 :     */
     499             668 :     explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
     500                 : 
     501                 :     /** Return a reference to the associated execution context.
     502                 : 
     503                 :         @return Reference to the context.
     504                 :     */
     505            1347 :     io_context& context() const noexcept
     506                 :     {
     507            1347 :         return *ctx_;
     508                 :     }
     509                 : 
     510                 :     /** Check if the current thread is running this executor's context.
     511                 : 
     512                 :         @return `true` if `run()` is being called on this thread.
     513                 :     */
     514            1359 :     bool running_in_this_thread() const noexcept
     515                 :     {
     516            1359 :         return ctx_->sched_->running_in_this_thread();
     517                 :     }
     518                 : 
     519                 :     /** Informs the executor that work is beginning.
     520                 : 
     521                 :         Must be paired with `on_work_finished()`.
     522                 :     */
     523            1504 :     void on_work_started() const noexcept
     524                 :     {
     525            1504 :         ctx_->sched_->work_started();
     526            1504 :     }
     527                 : 
     528                 :     /** Informs the executor that work has completed.
     529                 : 
     530                 :         @par Preconditions
     531                 :         A preceding call to `on_work_started()` on an equal executor.
     532                 :     */
     533            1478 :     void on_work_finished() const noexcept
     534                 :     {
     535            1478 :         ctx_->sched_->work_finished();
     536            1478 :     }
     537                 : 
     538                 :     /** Dispatch a continuation.
     539                 : 
     540                 :         Returns a handle for symmetric transfer. If called from
     541                 :         within `run()`, returns `c.h`. Otherwise posts the
     542                 :         enclosing continuation_op as a scheduler_op for later
     543                 :         execution and returns `std::noop_coroutine()`.
     544                 : 
     545                 :         @param c The continuation to dispatch. Must be the `cont`
     546                 :                  member of a `detail::continuation_op`.
     547                 : 
     548                 :         @return A handle for symmetric transfer or `std::noop_coroutine()`.
     549                 :     */
     550            1357 :     std::coroutine_handle<> dispatch(capy::continuation& c) const
     551                 :     {
     552            1357 :         if (running_in_this_thread())
     553             597 :             return c.h;
     554             760 :         post(c);
     555             760 :         return std::noop_coroutine();
     556                 :     }
     557                 : 
     558                 :     /** Post a continuation for deferred execution.
     559                 : 
     560                 :         If the continuation is backed by a continuation_op
     561                 :         (tagged), posts it directly as a scheduler_op — zero
     562                 :         heap allocation. Otherwise falls back to the
     563                 :         heap-allocating post(coroutine_handle<>) path.
     564                 :     */
     565            9340 :     void post(capy::continuation& c) const
     566                 :     {
     567            9340 :         auto* op = detail::continuation_op::try_from_continuation(c);
     568            9340 :         if (op)
     569            8577 :             ctx_->sched_->post(op);
     570                 :         else
     571             763 :             ctx_->sched_->post(c.h);
     572            9340 :     }
     573                 : 
     574                 :     /** Post a bare coroutine handle for deferred execution.
     575                 : 
     576                 :         Heap-allocates a scheduler_op to wrap the handle. Prefer
     577                 :         posting through a continuation_op-backed continuation when
     578                 :         the continuation has suitable lifetime.
     579                 : 
     580                 :         @param h The coroutine handle to post.
     581                 :     */
     582            1426 :     void post(std::coroutine_handle<> h) const
     583                 :     {
     584            1426 :         ctx_->sched_->post(h);
     585            1426 :     }
     586                 : 
     587                 :     /** Compare two executors for equality.
     588                 : 
     589                 :         @return `true` if both executors refer to the same context.
     590                 :     */
     591               1 :     bool operator==(executor_type const& other) const noexcept
     592                 :     {
     593               1 :         return ctx_ == other.ctx_;
     594                 :     }
     595                 : 
     596                 :     /** Compare two executors for inequality.
     597                 : 
     598                 :         @return `true` if the executors refer to different contexts.
     599                 :     */
     600                 :     bool operator!=(executor_type const& other) const noexcept
     601                 :     {
     602                 :         return ctx_ != other.ctx_;
     603                 :     }
     604                 : };
     605                 : 
     606                 : inline io_context::executor_type
     607             668 : io_context::get_executor() const noexcept
     608                 : {
     609             668 :     return executor_type(const_cast<io_context&>(*this));
     610                 : }
     611                 : 
     612                 : } // namespace boost::corosio
     613                 : 
     614                 : #endif // BOOST_COROSIO_IO_CONTEXT_HPP
        

Generated by: LCOV version 2.3