LCOV - code coverage report
Current view: top level - corosio - io_context.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 95.8 % 72 69 3
Test Date: 2026-03-26 16:40:44 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                 : namespace detail {
      31                 : struct timer_service_access;
      32                 : } // namespace detail
      33                 : 
      34                 : /** An I/O context for running asynchronous operations.
      35                 : 
      36                 :     The io_context provides an execution environment for async
      37                 :     operations. It maintains a queue of pending work items and
      38                 :     processes them when `run()` is called.
      39                 : 
      40                 :     The default and unsigned constructors select the platform's
      41                 :     native backend:
      42                 :     - Windows: IOCP
      43                 :     - Linux: epoll
      44                 :     - BSD/macOS: kqueue
      45                 :     - Other POSIX: select
      46                 : 
      47                 :     The template constructor accepts a backend tag value to
      48                 :     choose a specific backend at compile time:
      49                 : 
      50                 :     @par Example
      51                 :     @code
      52                 :     io_context ioc;                   // platform default
      53                 :     io_context ioc2(corosio::epoll);  // explicit backend
      54                 :     @endcode
      55                 : 
      56                 :     @par Thread Safety
      57                 :     Distinct objects: Safe.@n
      58                 :     Shared objects: Safe, if using a concurrency hint greater
      59                 :     than 1.
      60                 : 
      61                 :     @see epoll_t, select_t, kqueue_t, iocp_t
      62                 : */
      63                 : class BOOST_COROSIO_DECL io_context : public capy::execution_context
      64                 : {
      65                 :     friend struct detail::timer_service_access;
      66                 : 
      67                 : protected:
      68                 :     detail::scheduler* sched_;
      69                 : 
      70                 : public:
      71                 :     /** The executor type for this context. */
      72                 :     class executor_type;
      73                 : 
      74                 :     /** Construct with default concurrency and platform backend. */
      75                 :     io_context();
      76                 : 
      77                 :     /** Construct with a concurrency hint and platform backend.
      78                 : 
      79                 :         @param concurrency_hint Hint for the number of threads
      80                 :             that will call `run()`.
      81                 :     */
      82                 :     explicit io_context(unsigned concurrency_hint);
      83                 : 
      84                 :     /** Construct with an explicit backend tag.
      85                 : 
      86                 :         @param backend The backend tag value selecting the I/O
      87                 :             multiplexer (e.g. `corosio::epoll`).
      88                 :         @param concurrency_hint Hint for the number of threads
      89                 :             that will call `run()`.
      90                 :     */
      91                 :     template<class Backend>
      92                 :         requires requires { Backend::construct; }
      93 HIT         390 :     explicit io_context(
      94                 :         Backend backend,
      95                 :         unsigned concurrency_hint = std::thread::hardware_concurrency())
      96                 :         : capy::execution_context(this)
      97             390 :         , sched_(nullptr)
      98                 :     {
      99                 :         (void)backend;
     100             390 :         sched_ = &Backend::construct(*this, concurrency_hint);
     101             390 :     }
     102                 : 
     103                 :     ~io_context();
     104                 : 
     105                 :     io_context(io_context const&)            = delete;
     106                 :     io_context& operator=(io_context const&) = delete;
     107                 : 
     108                 :     /** Return an executor for this context.
     109                 : 
     110                 :         The returned executor can be used to dispatch coroutines
     111                 :         and post work items to this context.
     112                 : 
     113                 :         @return An executor associated with this context.
     114                 :     */
     115                 :     executor_type get_executor() const noexcept;
     116                 : 
     117                 :     /** Signal the context to stop processing.
     118                 : 
     119                 :         This causes `run()` to return as soon as possible. Any pending
     120                 :         work items remain queued.
     121                 :     */
     122               5 :     void stop()
     123                 :     {
     124               5 :         sched_->stop();
     125               5 :     }
     126                 : 
     127                 :     /** Return whether the context has been stopped.
     128                 : 
     129                 :         @return `true` if `stop()` has been called and `restart()`
     130                 :             has not been called since.
     131                 :     */
     132              21 :     bool stopped() const noexcept
     133                 :     {
     134              21 :         return sched_->stopped();
     135                 :     }
     136                 : 
     137                 :     /** Restart the context after being stopped.
     138                 : 
     139                 :         This function must be called before `run()` can be called
     140                 :         again after `stop()` has been called.
     141                 :     */
     142              91 :     void restart()
     143                 :     {
     144              91 :         sched_->restart();
     145              91 :     }
     146                 : 
     147                 :     /** Process all pending work items.
     148                 : 
     149                 :         This function blocks until all pending work items have been
     150                 :         executed or `stop()` is called. The context is stopped
     151                 :         when there is no more outstanding work.
     152                 : 
     153                 :         @note The context must be restarted with `restart()` before
     154                 :             calling this function again after it returns.
     155                 : 
     156                 :         @return The number of handlers executed.
     157                 :     */
     158             388 :     std::size_t run()
     159                 :     {
     160             388 :         return sched_->run();
     161                 :     }
     162                 : 
     163                 :     /** Process at most one pending work item.
     164                 : 
     165                 :         This function blocks until one work item has been executed
     166                 :         or `stop()` is called. The context is stopped when there
     167                 :         is no more outstanding work.
     168                 : 
     169                 :         @note The context must be restarted with `restart()` before
     170                 :             calling this function again after it returns.
     171                 : 
     172                 :         @return The number of handlers executed (0 or 1).
     173                 :     */
     174               2 :     std::size_t run_one()
     175                 :     {
     176               2 :         return sched_->run_one();
     177                 :     }
     178                 : 
     179                 :     /** Process work items for the specified duration.
     180                 : 
     181                 :         This function blocks until work items have been executed for
     182                 :         the specified duration, or `stop()` is called. The context
     183                 :         is stopped when there is no more outstanding work.
     184                 : 
     185                 :         @note The context must be restarted with `restart()` before
     186                 :             calling this function again after it returns.
     187                 : 
     188                 :         @param rel_time The duration for which to process work.
     189                 : 
     190                 :         @return The number of handlers executed.
     191                 :     */
     192                 :     template<class Rep, class Period>
     193               8 :     std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
     194                 :     {
     195               8 :         return run_until(std::chrono::steady_clock::now() + rel_time);
     196                 :     }
     197                 : 
     198                 :     /** Process work items until the specified time.
     199                 : 
     200                 :         This function blocks until the specified time is reached
     201                 :         or `stop()` is called. The context is stopped when there
     202                 :         is no more outstanding work.
     203                 : 
     204                 :         @note The context must be restarted with `restart()` before
     205                 :             calling this function again after it returns.
     206                 : 
     207                 :         @param abs_time The time point until which to process work.
     208                 : 
     209                 :         @return The number of handlers executed.
     210                 :     */
     211                 :     template<class Clock, class Duration>
     212                 :     std::size_t
     213               8 :     run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
     214                 :     {
     215               8 :         std::size_t n = 0;
     216              57 :         while (run_one_until(abs_time))
     217              49 :             if (n != (std::numeric_limits<std::size_t>::max)())
     218              49 :                 ++n;
     219               8 :         return n;
     220                 :     }
     221                 : 
     222                 :     /** Process at most one work item for the specified duration.
     223                 : 
     224                 :         This function blocks until one work item has been executed,
     225                 :         the specified duration has elapsed, or `stop()` is called.
     226                 :         The context is stopped when there is no more outstanding work.
     227                 : 
     228                 :         @note The context must be restarted with `restart()` before
     229                 :             calling this function again after it returns.
     230                 : 
     231                 :         @param rel_time The duration for which the call may block.
     232                 : 
     233                 :         @return The number of handlers executed (0 or 1).
     234                 :     */
     235                 :     template<class Rep, class Period>
     236               2 :     std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
     237                 :     {
     238               2 :         return run_one_until(std::chrono::steady_clock::now() + rel_time);
     239                 :     }
     240                 : 
     241                 :     /** Process at most one work item until the specified time.
     242                 : 
     243                 :         This function blocks until one work item has been executed,
     244                 :         the specified time is reached, or `stop()` is called.
     245                 :         The context is stopped when there is no more outstanding work.
     246                 : 
     247                 :         @note The context must be restarted with `restart()` before
     248                 :             calling this function again after it returns.
     249                 : 
     250                 :         @param abs_time The time point until which the call may block.
     251                 : 
     252                 :         @return The number of handlers executed (0 or 1).
     253                 :     */
     254                 :     template<class Clock, class Duration>
     255                 :     std::size_t
     256              61 :     run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
     257                 :     {
     258              61 :         typename Clock::time_point now = Clock::now();
     259              61 :         while (now < abs_time)
     260                 :         {
     261              61 :             auto rel_time = abs_time - now;
     262              61 :             if (rel_time > std::chrono::seconds(1))
     263 MIS           0 :                 rel_time = std::chrono::seconds(1);
     264                 : 
     265 HIT          61 :             std::size_t s = sched_->wait_one(
     266                 :                 static_cast<long>(
     267              61 :                     std::chrono::duration_cast<std::chrono::microseconds>(
     268                 :                         rel_time)
     269              61 :                         .count()));
     270                 : 
     271              61 :             if (s || stopped())
     272              61 :                 return s;
     273                 : 
     274 MIS           0 :             now = Clock::now();
     275                 :         }
     276               0 :         return 0;
     277                 :     }
     278                 : 
     279                 :     /** Process all ready work items without blocking.
     280                 : 
     281                 :         This function executes all work items that are ready to run
     282                 :         without blocking for more work. The context is stopped
     283                 :         when there is no more outstanding work.
     284                 : 
     285                 :         @note The context must be restarted with `restart()` before
     286                 :             calling this function again after it returns.
     287                 : 
     288                 :         @return The number of handlers executed.
     289                 :     */
     290 HIT           6 :     std::size_t poll()
     291                 :     {
     292               6 :         return sched_->poll();
     293                 :     }
     294                 : 
     295                 :     /** Process at most one ready work item without blocking.
     296                 : 
     297                 :         This function executes at most one work item that is ready
     298                 :         to run without blocking for more work. The context is
     299                 :         stopped when there is no more outstanding work.
     300                 : 
     301                 :         @note The context must be restarted with `restart()` before
     302                 :             calling this function again after it returns.
     303                 : 
     304                 :         @return The number of handlers executed (0 or 1).
     305                 :     */
     306               4 :     std::size_t poll_one()
     307                 :     {
     308               4 :         return sched_->poll_one();
     309                 :     }
     310                 : };
     311                 : 
     312                 : /** An executor for dispatching work to an I/O context.
     313                 : 
     314                 :     The executor provides the interface for posting work items and
     315                 :     dispatching coroutines to the associated context. It satisfies
     316                 :     the `capy::Executor` concept.
     317                 : 
     318                 :     Executors are lightweight handles that can be copied and compared
     319                 :     for equality. Two executors compare equal if they refer to the
     320                 :     same context.
     321                 : 
     322                 :     @par Thread Safety
     323                 :     Distinct objects: Safe.@n
     324                 :     Shared objects: Safe.
     325                 : */
     326                 : class io_context::executor_type
     327                 : {
     328                 :     io_context* ctx_ = nullptr;
     329                 : 
     330                 : public:
     331                 :     /** Default constructor.
     332                 : 
     333                 :         Constructs an executor not associated with any context.
     334                 :     */
     335                 :     executor_type() = default;
     336                 : 
     337                 :     /** Construct an executor from a context.
     338                 : 
     339                 :         @param ctx The context to associate with this executor.
     340                 :     */
     341             606 :     explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
     342                 : 
     343                 :     /** Return a reference to the associated execution context.
     344                 : 
     345                 :         @return Reference to the context.
     346                 :     */
     347            1278 :     io_context& context() const noexcept
     348                 :     {
     349            1278 :         return *ctx_;
     350                 :     }
     351                 : 
     352                 :     /** Check if the current thread is running this executor's context.
     353                 : 
     354                 :         @return `true` if `run()` is being called on this thread.
     355                 :     */
     356            1290 :     bool running_in_this_thread() const noexcept
     357                 :     {
     358            1290 :         return ctx_->sched_->running_in_this_thread();
     359                 :     }
     360                 : 
     361                 :     /** Informs the executor that work is beginning.
     362                 : 
     363                 :         Must be paired with `on_work_finished()`.
     364                 :     */
     365            1433 :     void on_work_started() const noexcept
     366                 :     {
     367            1433 :         ctx_->sched_->work_started();
     368            1433 :     }
     369                 : 
     370                 :     /** Informs the executor that work has completed.
     371                 : 
     372                 :         @par Preconditions
     373                 :         A preceding call to `on_work_started()` on an equal executor.
     374                 :     */
     375            1407 :     void on_work_finished() const noexcept
     376                 :     {
     377            1407 :         ctx_->sched_->work_finished();
     378            1407 :     }
     379                 : 
     380                 :     /** Dispatch a continuation.
     381                 : 
     382                 :         Returns a handle for symmetric transfer. If called from
     383                 :         within `run()`, returns `c.h`. Otherwise posts the
     384                 :         enclosing continuation_op as a scheduler_op for later
     385                 :         execution and returns `std::noop_coroutine()`.
     386                 : 
     387                 :         @param c The continuation to dispatch. Must be the `cont`
     388                 :                  member of a `detail::continuation_op`.
     389                 : 
     390                 :         @return A handle for symmetric transfer or `std::noop_coroutine()`.
     391                 :     */
     392            1288 :     std::coroutine_handle<> dispatch(capy::continuation& c) const
     393                 :     {
     394            1288 :         if (running_in_this_thread())
     395             600 :             return c.h;
     396             688 :         post(c);
     397             688 :         return std::noop_coroutine();
     398                 :     }
     399                 : 
     400                 :     /** Post a continuation for deferred execution.
     401                 : 
     402                 :         If the continuation is backed by a continuation_op
     403                 :         (tagged), posts it directly as a scheduler_op — zero
     404                 :         heap allocation. Otherwise falls back to the
     405                 :         heap-allocating post(coroutine_handle<>) path.
     406                 :     */
     407            9323 :     void post(capy::continuation& c) const
     408                 :     {
     409            9323 :         auto* op = detail::continuation_op::try_from_continuation(c);
     410            9323 :         if (op)
     411            8632 :             ctx_->sched_->post(op);
     412                 :         else
     413             691 :             ctx_->sched_->post(c.h);
     414            9323 :     }
     415                 : 
     416                 :     /** Post a bare coroutine handle for deferred execution.
     417                 : 
     418                 :         Heap-allocates a scheduler_op to wrap the handle. Prefer
     419                 :         posting through a continuation_op-backed continuation when
     420                 :         the continuation has suitable lifetime.
     421                 : 
     422                 :         @param h The coroutine handle to post.
     423                 :     */
     424            1426 :     void post(std::coroutine_handle<> h) const
     425                 :     {
     426            1426 :         ctx_->sched_->post(h);
     427            1426 :     }
     428                 : 
     429                 :     /** Compare two executors for equality.
     430                 : 
     431                 :         @return `true` if both executors refer to the same context.
     432                 :     */
     433               1 :     bool operator==(executor_type const& other) const noexcept
     434                 :     {
     435               1 :         return ctx_ == other.ctx_;
     436                 :     }
     437                 : 
     438                 :     /** Compare two executors for inequality.
     439                 : 
     440                 :         @return `true` if the executors refer to different contexts.
     441                 :     */
     442                 :     bool operator!=(executor_type const& other) const noexcept
     443                 :     {
     444                 :         return ctx_ != other.ctx_;
     445                 :     }
     446                 : };
     447                 : 
     448                 : inline io_context::executor_type
     449             606 : io_context::get_executor() const noexcept
     450                 : {
     451             606 :     return executor_type(const_cast<io_context&>(*this));
     452                 : }
     453                 : 
     454                 : } // namespace boost::corosio
     455                 : 
     456                 : #endif // BOOST_COROSIO_IO_CONTEXT_HPP
        

Generated by: LCOV version 2.3