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

Generated by: LCOV version 2.3