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

Generated by: LCOV version 2.3