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:
  113 + /// Construct an awaitable that waits for `dur` nanoseconds.
  114 + /// Prefer the @ref delay factory over constructing directly.
HITCBC 113   29 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept 115   29 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
HITCBC 114   29 : dur_(dur) 116   29 : dur_(dur)
115   { 117   {
HITCBC 116   29 } 118   29 }
117   119  
118   /// @pre The stop callback must not be active 120   /// @pre The stop callback must not be active
119   /// (i.e. the object has not yet been awaited). 121   /// (i.e. the object has not yet been awaited).
HITCBC 120   46 delay_awaitable(delay_awaitable&& o) noexcept 122   46 delay_awaitable(delay_awaitable&& o) noexcept
HITCBC 121   46 : dur_(o.dur_) 123   46 : dur_(o.dur_)
HITCBC 122   46 , ts_(o.ts_) 124   46 , ts_(o.ts_)
HITCBC 123   46 , tid_(o.tid_) 125   46 , tid_(o.tid_)
HITCBC 124   46 , cont_(o.cont_) 126   46 , cont_(o.cont_)
HITCBC 125   46 , claimed_(o.claimed_.load(std::memory_order_relaxed)) 127   46 , claimed_(o.claimed_.load(std::memory_order_relaxed))
HITCBC 126   46 , canceled_(o.canceled_) 128   46 , canceled_(o.canceled_)
HITCBC 127   46 , stop_cb_active_(std::exchange(o.stop_cb_active_, false)) 129   46 , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
128   { 130   {
HITCBC 129   46 } 131   46 }
130   132  
  133 + /// Tear down any registered stop callback and cancel the
  134 + /// pending timer if one is still scheduled.
HITCBC 131   75 ~delay_awaitable() 135   75 ~delay_awaitable()
132   { 136   {
HITCBC 133   75 if(stop_cb_active_) 137   75 if(stop_cb_active_)
HITCBC 134   2 stop_cb_().~stop_cb_t(); 138   2 stop_cb_().~stop_cb_t();
HITCBC 135   75 if(ts_) 139   75 if(ts_)
HITCBC 136   20 ts_->cancel(tid_); 140   20 ts_->cancel(tid_);
HITCBC 137   75 } 141   75 }
138   142  
139   delay_awaitable(delay_awaitable const&) = delete; 143   delay_awaitable(delay_awaitable const&) = delete;
140   delay_awaitable& operator=(delay_awaitable const&) = delete; 144   delay_awaitable& operator=(delay_awaitable const&) = delete;
141   delay_awaitable& operator=(delay_awaitable&&) = delete; 145   delay_awaitable& operator=(delay_awaitable&&) = delete;
142   146  
  147 + /// Return true for zero or negative durations, completing
  148 + /// synchronously without scheduling a timer.
HITCBC 143   28 bool await_ready() const noexcept 149   28 bool await_ready() const noexcept
144   { 150   {
HITCBC 145   28 return dur_.count() <= 0; 151   28 return dur_.count() <= 0;
146   } 152   }
147   153  
  154 + /// Suspend the coroutine, scheduling the timer and a stop
  155 + /// callback on the environment's executor and stop token.
  156 + /// Resumes `h` immediately if stop was already requested.
148   std::coroutine_handle<> 157   std::coroutine_handle<>
HITCBC 149   26 await_suspend( 158   26 await_suspend(
150   std::coroutine_handle<> h, 159   std::coroutine_handle<> h,
151   io_env const* env) noexcept 160   io_env const* env) noexcept
152   { 161   {
153   // Already stopped: resume immediately 162   // Already stopped: resume immediately
HITCBC 154   26 if(env->stop_token.stop_requested()) 163   26 if(env->stop_token.stop_requested())
155   { 164   {
HITCBC 156   6 canceled_ = true; 165   6 canceled_ = true;
HITCBC 157   6 return h; 166   6 return h;
158   } 167   }
159   168  
HITCBC 160   20 cont_.h = h; 169   20 cont_.h = h;
HITCBC 161   20 ts_ = &env->executor.context().use_service<detail::timer_service>(); 170   20 ts_ = &env->executor.context().use_service<detail::timer_service>();
162   171  
163   // Schedule timer (won't fire inline since deadline is in the future) 172   // Schedule timer (won't fire inline since deadline is in the future)
HITCBC 164   20 tid_ = ts_->schedule_after(dur_, 173   20 tid_ = ts_->schedule_after(dur_,
HITCBC 165   20 [this, ex = env->executor]() 174   20 [this, ex = env->executor]()
166   { 175   {
HITCBC 167   17 if(!claimed_.exchange( 176   17 if(!claimed_.exchange(
168   true, std::memory_order_acq_rel)) 177   true, std::memory_order_acq_rel))
169   { 178   {
HITCBC 170   17 ex.post(cont_); 179   17 ex.post(cont_);
171   } 180   }
HITCBC 172   17 }); 181   17 });
173   182  
174   // Register stop callback (may fire inline) 183   // Register stop callback (may fire inline)
HITCBC 175   60 ::new(stop_cb_buf_) stop_cb_t( 184   60 ::new(stop_cb_buf_) stop_cb_t(
HITCBC 176   20 env->stop_token, 185   20 env->stop_token,
HITCBC 177   20 cancel_fn{this, env->executor}); 186   20 cancel_fn{this, env->executor});
HITCBC 178   20 stop_cb_active_ = true; 187   20 stop_cb_active_ = true;
179   188  
HITCBC 180   20 return std::noop_coroutine(); 189   20 return std::noop_coroutine();
181   } 190   }
182   191  
  192 + /// Clean up the stop callback and timer, then return
  193 + /// `io_result<>{error::canceled}` if cancellation claimed
  194 + /// the resume, or an empty `io_result<>` otherwise.
HITCBC 183   27 io_result<> await_resume() noexcept 195   27 io_result<> await_resume() noexcept
184   { 196   {
HITCBC 185   27 if(stop_cb_active_) 197   27 if(stop_cb_active_)
186   { 198   {
HITCBC 187   18 stop_cb_().~stop_cb_t(); 199   18 stop_cb_().~stop_cb_t();
HITCBC 188   18 stop_cb_active_ = false; 200   18 stop_cb_active_ = false;
189   } 201   }
HITCBC 190   27 if(ts_) 202   27 if(ts_)
HITCBC 191   18 ts_->cancel(tid_); 203   18 ts_->cancel(tid_);
HITCBC 192   27 if(canceled_) 204   27 if(canceled_)
HITCBC 193   7 return io_result<>{make_error_code(error::canceled)}; 205   7 return io_result<>{make_error_code(error::canceled)};
HITCBC 194   20 return io_result<>{}; 206   20 return io_result<>{};
195   } 207   }
196   }; 208   };
197   209  
198   /** Suspend the current coroutine for a duration. 210   /** Suspend the current coroutine for a duration.
199   211  
200   Returns an IoAwaitable that completes at or after the 212   Returns an IoAwaitable that completes at or after the
201   specified duration, or earlier if the environment's stop 213   specified duration, or earlier if the environment's stop
202   token is activated. 214   token is activated.
203   215  
204   Zero or negative durations complete synchronously without 216   Zero or negative durations complete synchronously without
205   scheduling a timer. 217   scheduling a timer.
206   218  
207   @par Example 219   @par Example
208   @code 220   @code
209   auto [ec] = co_await delay(std::chrono::milliseconds(100)); 221   auto [ec] = co_await delay(std::chrono::milliseconds(100));
210   @endcode 222   @endcode
211   223  
212   @param dur The duration to wait. 224   @param dur The duration to wait.
213   225  
214   @return A @ref delay_awaitable whose `await_resume` 226   @return A @ref delay_awaitable whose `await_resume`
215   returns `io_result<>`. On normal completion, `ec` 227   returns `io_result<>`. On normal completion, `ec`
216   is clear. On cancellation, `ec == error::canceled`. 228   is clear. On cancellation, `ec == error::canceled`.
217   229  
218   @throws Nothing. 230   @throws Nothing.
219   231  
220   @see timeout, delay_awaitable 232   @see timeout, delay_awaitable
221   */ 233   */
222   template<typename Rep, typename Period> 234   template<typename Rep, typename Period>
223   delay_awaitable 235   delay_awaitable
HITCBC 224   28 delay(std::chrono::duration<Rep, Period> dur) noexcept 236   28 delay(std::chrono::duration<Rep, Period> dur) noexcept
225   { 237   {
226   return delay_awaitable{ 238   return delay_awaitable{
HITCBC 227   28 std::chrono::duration_cast<std::chrono::nanoseconds>(dur)}; 239   28 std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
228   } 240   }
229   241  
230   } // capy 242   } // capy
231   } // boost 243   } // boost
232   244  
233   #endif 245   #endif