LCOV - code coverage report
Current view: top level - corosio - delay.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 93.3 % 45 42 3
Test Date: 2026-07-17 17:14:45 Functions: 100.0 % 12 12

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_DELAY_HPP
      11                 : #define BOOST_COROSIO_DELAY_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/config.hpp>
      14                 : #include <boost/corosio/detail/except.hpp>
      15                 : #include <boost/corosio/detail/timer.hpp>
      16                 : #include <boost/capy/error.hpp>
      17                 : #include <boost/capy/ex/io_env.hpp>
      18                 : #include <boost/capy/io_result.hpp>
      19                 : 
      20                 : #include <chrono>
      21                 : #include <coroutine>
      22                 : #include <exception>
      23                 : #include <optional>
      24                 : #include <stdexcept>
      25                 : 
      26                 : namespace boost::corosio {
      27                 : 
      28                 : /** IoAwaitable returned by @ref delay.
      29                 : 
      30                 :     Suspends the calling coroutine until the deadline elapses or
      31                 :     the environment's stop token is activated, whichever comes
      32                 :     first. A deadline already elapsed at suspension, or a stop
      33                 :     token already active, resumes the coroutine inline, without
      34                 :     starting a timer (see Cancellation below). Otherwise the
      35                 :     coroutine resumes through the executor once the timer fires
      36                 :     or a mid-wait cancellation arrives.
      37                 : 
      38                 :     Not intended to be named directly; use the @ref delay factory
      39                 :     overloads instead.
      40                 : 
      41                 :     @par Preconditions
      42                 :     The awaiting coroutine's executor must belong to an
      43                 :     `io_context`. Any other execution context terminates with a
      44                 :     diagnostic, because silently running without a timer would
      45                 :     drop the requested delay.
      46                 : 
      47                 :     @par Cancellation
      48                 :     If stop is already requested before suspension, the coroutine
      49                 :     resumes immediately with `error::canceled`. If stop is
      50                 :     requested while suspended, the pending wait is cancelled and
      51                 :     the coroutine resumes with `error::canceled`. Requesting stop
      52                 :     from another thread while the io_context runs in
      53                 :     single_threaded mode (auto-enabled at concurrency_hint == 1)
      54                 :     is not permitted by io_context's threading rules;
      55                 :     cross-thread cancellation requires a multi-threaded-capable
      56                 :     context.
      57                 : 
      58                 :     @see delay
      59                 : */
      60                 : class delay_awaitable
      61                 : {
      62                 :     // wait() names timer's private awaitable type; decltype is
      63                 :     // the only way to store it here.
      64                 :     using wait_type = decltype(std::declval<detail::timer&>().wait());
      65                 : 
      66                 :     std::chrono::steady_clock::time_point deadline_{};
      67                 :     std::chrono::nanoseconds dur_{};
      68                 :     bool has_deadline_ = false;
      69                 :     bool canceled_ = false;
      70                 :     std::optional<detail::timer> timer_;
      71                 :     std::optional<wait_type> wait_;
      72                 : 
      73                 : public:
      74                 :     /// Construct an awaitable that waits for `dur` nanoseconds.
      75 HIT       10424 :     explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
      76           10424 :         : dur_(dur)
      77                 :     {
      78           10424 :     }
      79                 : 
      80                 :     /// Construct an awaitable that waits until `tp`.
      81               6 :     explicit delay_awaitable(
      82                 :         std::chrono::steady_clock::time_point tp) noexcept
      83               6 :         : deadline_(tp)
      84               6 :         , has_deadline_(true)
      85                 :     {
      86               6 :     }
      87                 : 
      88                 :     /// Construct by transferring state from `other`.
      89                 :     // Only moved before await_suspend; wait_ is engaged after.
      90           12458 :     delay_awaitable(delay_awaitable&&) = default;
      91                 : 
      92                 :     delay_awaitable(delay_awaitable const&) = delete;
      93                 :     delay_awaitable& operator=(delay_awaitable const&) = delete;
      94                 :     delay_awaitable& operator=(delay_awaitable&&) = delete;
      95                 : 
      96                 :     /// Return false unconditionally; see await_suspend.
      97                 :     // The elapsed-deadline fast path must run after the stop-token
      98                 :     // check, and only await_suspend receives the env carrying it.
      99            8400 :     bool await_ready() const noexcept
     100                 :     {
     101            8400 :         return false;
     102                 :     }
     103                 : 
     104                 :     /// Resume inline if stopped or elapsed; else wait on a timer.
     105                 :     std::coroutine_handle<>
     106           10430 :     await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
     107                 :     {
     108           10430 :         if(env->stop_token.stop_requested())
     109                 :         {
     110            4003 :             canceled_ = true;
     111            4003 :             return h;
     112                 :         }
     113                 : 
     114                 :         // Elapsed deadlines complete synchronously, but only once a
     115                 :         // pending stop request has already been ruled out above.
     116           12850 :         if(has_deadline_ ?
     117            6427 :             deadline_ <= std::chrono::steady_clock::now() :
     118            6423 :             dur_.count() <= 0)
     119               8 :             return h;
     120                 : 
     121                 :         // A non-io_context executor cannot supply a timer service,
     122                 :         // and await_suspend is driven through a noexcept wrapper, so
     123                 :         // translate the service-lookup failure into a clear terminate.
     124                 :         try
     125                 :         {
     126            6419 :             timer_.emplace(env->executor.context());
     127                 :         }
     128               2 :         catch(std::logic_error const&)
     129                 :         {
     130               2 :             detail::throw_logic_error(
     131                 :                 "delay requires an io_context-backed executor");
     132               2 :         }
     133 MIS           0 :         catch(std::exception const& e)
     134                 :         {
     135               0 :             detail::throw_logic_error(e.what());
     136               0 :         }
     137                 : 
     138 HIT        6417 :         if(has_deadline_)
     139               2 :             timer_->expires_at(deadline_);
     140                 :         else
     141            6415 :             timer_->expires_after(dur_);
     142                 : 
     143            6417 :         wait_.emplace(timer_->wait());
     144            6417 :         return wait_->await_suspend(h, env);
     145                 :     }
     146                 : 
     147                 :     /// Return empty on expiry, `error::canceled` if stop won.
     148           10406 :     capy::io_result<> await_resume() noexcept
     149                 :     {
     150           10406 :         if(canceled_)
     151            4003 :             return {capy::error::canceled};
     152            6403 :         if(wait_)
     153            6395 :             return wait_->await_resume();
     154               8 :         return {};
     155                 :     }
     156                 : };
     157                 : 
     158                 : /** Suspend the current coroutine for a duration.
     159                 : 
     160                 :     Returns an IoAwaitable that completes at or after the
     161                 :     specified duration, or earlier if the environment's stop
     162                 :     token is activated. Zero or negative durations complete
     163                 :     synchronously.
     164                 : 
     165                 :     @par Example
     166                 :     @code
     167                 :     auto [ec] = co_await delay(std::chrono::milliseconds(100));
     168                 :     @endcode
     169                 : 
     170                 :     @param dur The duration to wait.
     171                 : 
     172                 :     @return A @ref delay_awaitable yielding `io_result<>`.
     173                 : */
     174                 : template<typename Rep, typename Period>
     175                 : [[nodiscard]] delay_awaitable
     176           10422 : delay(std::chrono::duration<Rep, Period> dur) noexcept
     177                 : {
     178                 :     using namespace std::chrono;
     179                 :     // Narrow reps wrap if nanoseconds::max() is converted into them;
     180                 :     // a double comparison clamps safely in both directions.
     181                 :     using dsec = duration<double>;
     182           18792 :     auto ns = dsec(dur) >= dsec((nanoseconds::max)())
     183           18792 :         ? (nanoseconds::max)()
     184           10420 :         : dsec(dur) <= dsec((nanoseconds::min)())
     185           10420 :             ? (nanoseconds::min)()
     186           10418 :             : duration_cast<nanoseconds>(dur);
     187           10422 :     return delay_awaitable(ns);
     188                 : }
     189                 : 
     190                 : /** Suspend the current coroutine until a time point.
     191                 : 
     192                 :     Returns an IoAwaitable that completes at or after `tp`, or
     193                 :     earlier if the environment's stop token is activated. Time
     194                 :     points already reached complete synchronously.
     195                 : 
     196                 :     @param tp The steady-clock time point to wait until.
     197                 : 
     198                 :     @return A @ref delay_awaitable yielding `io_result<>`.
     199                 : */
     200                 : [[nodiscard]] inline delay_awaitable
     201               6 : delay(std::chrono::steady_clock::time_point tp) noexcept
     202                 : {
     203               6 :     return delay_awaitable(tp);
     204                 : }
     205                 : 
     206                 : } // namespace boost::corosio
     207                 : 
     208                 : #endif
        

Generated by: LCOV version 2.3