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-12 20:08:21 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                 :     BOOST_CAPY_MSVC_WARNING_PUSH
      92                 :     BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
      93                 :     alignas(stop_cb_t)
      94                 :         unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
      95                 :     BOOST_CAPY_MSVC_WARNING_POP
      96                 : 
      97              19 :     stop_cb_t& stop_cb_() noexcept
      98                 :     {
      99              19 :         return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
     100                 :     }
     101                 : 
     102                 : public:
     103              27 :     explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
     104              27 :         : dur_(dur)
     105                 :     {
     106              27 :     }
     107                 : 
     108                 :     /// @pre The stop callback must not be active
     109                 :     ///      (i.e. the object has not yet been awaited).
     110              58 :     delay_awaitable(delay_awaitable&& o) noexcept
     111              58 :         : dur_(o.dur_)
     112              58 :         , ts_(o.ts_)
     113              58 :         , tid_(o.tid_)
     114              58 :         , claimed_(o.claimed_.load(std::memory_order_relaxed))
     115              58 :         , canceled_(o.canceled_)
     116              58 :         , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
     117                 :     {
     118              58 :     }
     119                 : 
     120              85 :     ~delay_awaitable()
     121                 :     {
     122              85 :         if(stop_cb_active_)
     123               1 :             stop_cb_().~stop_cb_t();
     124              85 :         if(ts_)
     125              19 :             ts_->cancel(tid_);
     126              85 :     }
     127                 : 
     128                 :     delay_awaitable(delay_awaitable const&) = delete;
     129                 :     delay_awaitable& operator=(delay_awaitable const&) = delete;
     130                 :     delay_awaitable& operator=(delay_awaitable&&) = delete;
     131                 : 
     132              26 :     bool await_ready() const noexcept
     133                 :     {
     134              26 :         return dur_.count() <= 0;
     135                 :     }
     136                 : 
     137                 :     std::coroutine_handle<>
     138              24 :     await_suspend(
     139                 :         std::coroutine_handle<> h,
     140                 :         io_env const* env) noexcept
     141                 :     {
     142                 :         // Already stopped: resume immediately
     143              24 :         if(env->stop_token.stop_requested())
     144                 :         {
     145               5 :             canceled_ = true;
     146               5 :             return h;
     147                 :         }
     148                 : 
     149              19 :         ts_ = &env->executor.context().use_service<detail::timer_service>();
     150                 : 
     151                 :         // Schedule timer (won't fire inline since deadline is in the future)
     152              19 :         tid_ = ts_->schedule_after(dur_,
     153              19 :             [this, h, ex = env->executor]()
     154                 :             {
     155              17 :                 if(!claimed_.exchange(
     156                 :                     true, std::memory_order_acq_rel))
     157                 :                 {
     158              17 :                     ex.post(h);
     159                 :                 }
     160              17 :             });
     161                 : 
     162                 :         // Register stop callback (may fire inline)
     163              57 :         ::new(stop_cb_buf_) stop_cb_t(
     164              19 :             env->stop_token,
     165              19 :             cancel_fn{this, env->executor, h});
     166              19 :         stop_cb_active_ = true;
     167                 : 
     168              19 :         return std::noop_coroutine();
     169                 :     }
     170                 : 
     171              26 :     void await_resume() noexcept
     172                 :     {
     173              26 :         if(stop_cb_active_)
     174                 :         {
     175              18 :             stop_cb_().~stop_cb_t();
     176              18 :             stop_cb_active_ = false;
     177                 :         }
     178              26 :         if(ts_)
     179              18 :             ts_->cancel(tid_);
     180              26 :     }
     181                 : };
     182                 : 
     183                 : /** Suspend the current coroutine for a duration.
     184                 : 
     185                 :     Returns an IoAwaitable that completes at or after the
     186                 :     specified duration, or earlier if the environment's stop
     187                 :     token is activated. Completion is always normal (void
     188                 :     return); no exception is thrown on cancellation.
     189                 : 
     190                 :     Zero or negative durations complete synchronously without
     191                 :     scheduling a timer.
     192                 : 
     193                 :     @par Example
     194                 :     @code
     195                 :     co_await delay(std::chrono::milliseconds(100));
     196                 :     @endcode
     197                 : 
     198                 :     @param dur The duration to wait.
     199                 : 
     200                 :     @return A @ref delay_awaitable whose `await_resume`
     201                 :         returns `void`.
     202                 : 
     203                 :     @throws Nothing.
     204                 : 
     205                 :     @see timeout, delay_awaitable
     206                 : */
     207                 : template<typename Rep, typename Period>
     208                 : delay_awaitable
     209              26 : delay(std::chrono::duration<Rep, Period> dur) noexcept
     210                 : {
     211                 :     return delay_awaitable{
     212              26 :         std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
     213                 : }
     214                 : 
     215                 : } // capy
     216                 : } // boost
     217                 : 
     218                 : #endif
        

Generated by: LCOV version 2.3