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