LCOV - code coverage report
Current view: top level - capy - delay.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 50 50
Test Date: 2026-03-11 23:32:43 Functions: 100.0 % 11 11

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       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/capy
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_CAPY_DELAY_HPP
      11                 : #define BOOST_CAPY_DELAY_HPP
      12                 : 
      13                 : #include <boost/capy/detail/config.hpp>
      14                 : #include <boost/capy/ex/executor_ref.hpp>
      15                 : #include <boost/capy/ex/io_env.hpp>
      16                 : #include <boost/capy/ex/detail/timer_service.hpp>
      17                 : 
      18                 : #include <atomic>
      19                 : #include <chrono>
      20                 : #include <coroutine>
      21                 : #include <new>
      22                 : #include <stop_token>
      23                 : #include <utility>
      24                 : 
      25                 : namespace boost {
      26                 : namespace capy {
      27                 : 
      28                 : /** IoAwaitable returned by @ref delay.
      29                 : 
      30                 :     Suspends the calling coroutine until the deadline elapses
      31                 :     or the environment's stop token is activated, whichever
      32                 :     comes first. Resumption is always posted through the
      33                 :     executor, never inline on the timer thread.
      34                 : 
      35                 :     Not intended to be named directly; use the @ref delay
      36                 :     factory function instead.
      37                 : 
      38                 :     @par Cancellation
      39                 : 
      40                 :     If `stop_requested()` is true before suspension, the
      41                 :     coroutine resumes immediately without scheduling a timer.
      42                 :     If stop is requested while suspended, the stop callback
      43                 :     claims the resume and posts it through the executor; the
      44                 :     pending timer is cancelled on the next `await_resume` or
      45                 :     destructor call.
      46                 : 
      47                 :     @par Thread Safety
      48                 : 
      49                 :     A single `delay_awaitable` must not be awaited concurrently.
      50                 :     Multiple independent `delay()` calls on the same
      51                 :     execution_context are safe and share one timer thread.
      52                 : 
      53                 :     @see delay, timeout
      54                 : */
      55                 : class delay_awaitable
      56                 : {
      57                 :     std::chrono::nanoseconds dur_;
      58                 : 
      59                 :     detail::timer_service* ts_ = nullptr;
      60                 :     detail::timer_service::timer_id tid_ = 0;
      61                 : 
      62                 :     // Declared before stop_cb_buf_: the callback
      63                 :     // accesses these members, so they must still be
      64                 :     // alive if the stop_cb_ destructor blocks.
      65                 :     std::atomic<bool> claimed_{false};
      66                 :     bool canceled_ = false;
      67                 :     bool stop_cb_active_ = false;
      68                 : 
      69                 :     struct cancel_fn
      70                 :     {
      71                 :         delay_awaitable* self_;
      72                 :         executor_ref ex_;
      73                 :         std::coroutine_handle<> h_;
      74                 : 
      75 HIT           1 :         void operator()() const noexcept
      76                 :         {
      77               1 :             if(!self_->claimed_.exchange(
      78                 :                 true, std::memory_order_acq_rel))
      79                 :             {
      80               1 :                 self_->canceled_ = true;
      81               1 :                 ex_.post(h_);
      82                 :             }
      83               1 :         }
      84                 :     };
      85                 : 
      86                 :     using stop_cb_t = std::stop_callback<cancel_fn>;
      87                 : 
      88                 :     // Aligned storage for the stop callback.
      89                 :     // Declared last: its destructor may block while
      90                 :     // the callback accesses the members above.
      91                 : #ifdef _MSC_VER
      92                 : # pragma warning(push)
      93                 : # pragma warning(disable: 4324)
      94                 : #endif
      95                 :     alignas(stop_cb_t)
      96                 :         unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
      97                 : #ifdef _MSC_VER
      98                 : # pragma warning(pop)
      99                 : #endif
     100                 : 
     101              10 :     stop_cb_t& stop_cb_() noexcept
     102                 :     {
     103              10 :         return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
     104                 :     }
     105                 : 
     106                 : public:
     107              17 :     explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
     108              17 :         : dur_(dur)
     109                 :     {
     110              17 :     }
     111                 : 
     112                 :     /// @pre The stop callback must not be active
     113                 :     ///      (i.e. the object has not yet been awaited).
     114              48 :     delay_awaitable(delay_awaitable&& o) noexcept
     115              48 :         : dur_(o.dur_)
     116              48 :         , ts_(o.ts_)
     117              48 :         , tid_(o.tid_)
     118              48 :         , claimed_(o.claimed_.load(std::memory_order_relaxed))
     119              48 :         , canceled_(o.canceled_)
     120              48 :         , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
     121                 :     {
     122              48 :     }
     123                 : 
     124              65 :     ~delay_awaitable()
     125                 :     {
     126              65 :         if(stop_cb_active_)
     127               1 :             stop_cb_().~stop_cb_t();
     128              65 :         if(ts_)
     129              10 :             ts_->cancel(tid_);
     130              65 :     }
     131                 : 
     132                 :     delay_awaitable(delay_awaitable const&) = delete;
     133                 :     delay_awaitable& operator=(delay_awaitable const&) = delete;
     134                 :     delay_awaitable& operator=(delay_awaitable&&) = delete;
     135                 : 
     136              16 :     bool await_ready() const noexcept
     137                 :     {
     138              16 :         return dur_.count() <= 0;
     139                 :     }
     140                 : 
     141                 :     std::coroutine_handle<>
     142              15 :     await_suspend(
     143                 :         std::coroutine_handle<> h,
     144                 :         io_env const* env) noexcept
     145                 :     {
     146                 :         // Already stopped: resume immediately
     147              15 :         if(env->stop_token.stop_requested())
     148                 :         {
     149               5 :             canceled_ = true;
     150               5 :             return h;
     151                 :         }
     152                 : 
     153              10 :         ts_ = &env->executor.context().use_service<detail::timer_service>();
     154                 : 
     155                 :         // Schedule timer (won't fire inline since deadline is in the future)
     156              10 :         tid_ = ts_->schedule_after(dur_,
     157              10 :             [this, h, ex = env->executor]()
     158                 :             {
     159               8 :                 if(!claimed_.exchange(
     160                 :                     true, std::memory_order_acq_rel))
     161                 :                 {
     162               8 :                     ex.post(h);
     163                 :                 }
     164               8 :             });
     165                 : 
     166                 :         // Register stop callback (may fire inline)
     167              30 :         ::new(stop_cb_buf_) stop_cb_t(
     168              10 :             env->stop_token,
     169              10 :             cancel_fn{this, env->executor, h});
     170              10 :         stop_cb_active_ = true;
     171                 : 
     172              10 :         return std::noop_coroutine();
     173                 :     }
     174                 : 
     175              16 :     void await_resume() noexcept
     176                 :     {
     177              16 :         if(stop_cb_active_)
     178                 :         {
     179               9 :             stop_cb_().~stop_cb_t();
     180               9 :             stop_cb_active_ = false;
     181                 :         }
     182              16 :         if(ts_)
     183               9 :             ts_->cancel(tid_);
     184              16 :     }
     185                 : };
     186                 : 
     187                 : /** Suspend the current coroutine for a duration.
     188                 : 
     189                 :     Returns an IoAwaitable that completes at or after the
     190                 :     specified duration, or earlier if the environment's stop
     191                 :     token is activated. Completion is always normal (void
     192                 :     return); no exception is thrown on cancellation.
     193                 : 
     194                 :     Zero or negative durations complete synchronously without
     195                 :     scheduling a timer.
     196                 : 
     197                 :     @par Example
     198                 :     @code
     199                 :     co_await delay(std::chrono::milliseconds(100));
     200                 :     @endcode
     201                 : 
     202                 :     @param dur The duration to wait.
     203                 : 
     204                 :     @return A @ref delay_awaitable whose `await_resume`
     205                 :         returns `void`.
     206                 : 
     207                 :     @throws Nothing.
     208                 : 
     209                 :     @see timeout, delay_awaitable
     210                 : */
     211                 : template<typename Rep, typename Period>
     212                 : delay_awaitable
     213              16 : delay(std::chrono::duration<Rep, Period> dur) noexcept
     214                 : {
     215                 :     return delay_awaitable{
     216              16 :         std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
     217                 : }
     218                 : 
     219                 : } // capy
     220                 : } // boost
     221                 : 
     222                 : #endif
        

Generated by: LCOV version 2.3