94.44% Lines (85/90) 100.00% Functions (18/18)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   #ifndef BOOST_COROSIO_DELAY_HPP 11   #ifndef BOOST_COROSIO_DELAY_HPP
12   #define BOOST_COROSIO_DELAY_HPP 12   #define BOOST_COROSIO_DELAY_HPP
13   13  
14   #include <boost/corosio/detail/config.hpp> 14   #include <boost/corosio/detail/config.hpp>
15   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
16   #include <boost/corosio/detail/timer.hpp> 16   #include <boost/corosio/detail/timer.hpp>
17   #include <boost/corosio/wait_traits.hpp> 17   #include <boost/corosio/wait_traits.hpp>
18   #include <boost/capy/error.hpp> 18   #include <boost/capy/error.hpp>
19   #include <boost/capy/ex/io_env.hpp> 19   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
21   21  
22   #include <chrono> 22   #include <chrono>
23   #include <concepts> 23   #include <concepts>
24   #include <coroutine> 24   #include <coroutine>
25   #include <exception> 25   #include <exception>
26   #include <optional> 26   #include <optional>
27   #include <stdexcept> 27   #include <stdexcept>
28   #include <system_error> 28   #include <system_error>
29   #include <type_traits> 29   #include <type_traits>
30   30  
31   namespace boost::corosio { 31   namespace boost::corosio {
32   32  
33   namespace detail { 33   namespace detail {
34   34  
35   // Narrow reps wrap if nanoseconds::max() is converted into them; 35   // Narrow reps wrap if nanoseconds::max() is converted into them;
36   // a double comparison clamps safely in both directions. 36   // a double comparison clamps safely in both directions.
37   template<typename Rep, typename Period> 37   template<typename Rep, typename Period>
38   std::chrono::nanoseconds 38   std::chrono::nanoseconds
HITCBC 39   21018 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 39   20878 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept
40   { 40   {
41   using namespace std::chrono; 41   using namespace std::chrono;
42   using dsec = duration<double>; 42   using dsec = duration<double>;
43   if constexpr (std::is_floating_point_v<Rep>) 43   if constexpr (std::is_floating_point_v<Rep>)
44   { 44   {
45   // NaN fails both clamp comparisons and would reach the 45   // NaN fails both clamp comparisons and would reach the
46   // cast; treat it as no wait rather than undefined behavior. 46   // cast; treat it as no wait rather than undefined behavior.
HITCBC 47   2 if (dur != dur) 47   2 if (dur != dur)
HITCBC 48   2 return nanoseconds::zero(); 48   2 return nanoseconds::zero();
49   } 49   }
HITCBC 50   21016 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)() 50   20876 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)()
HITCBC 51   42030 : dsec(dur) <= dsec((nanoseconds::min)()) 51   41750 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 52   21014 ? (nanoseconds::min)() 52   20874 ? (nanoseconds::min)()
HITCBC 53   21016 : duration_cast<nanoseconds>(dur); 53   20876 : duration_cast<nanoseconds>(dur);
54   } 54   }
55   55  
56   // A non-io_context executor cannot supply a timer service, and 56   // A non-io_context executor cannot supply a timer service, and
57   // await_suspend is driven through a noexcept wrapper, so translate 57   // await_suspend is driven through a noexcept wrapper, so translate
58   // the service-lookup failure into a clear terminate. 58   // the service-lookup failure into a clear terminate.
59   inline void 59   inline void
HITCBC 60   13102 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx) 60   13219 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx)
61   { 61   {
62   try 62   try
63   { 63   {
HITCBC 64   13102 t.emplace(ctx); 64   13219 t.emplace(ctx);
65   } 65   }
HITCBC 66   2 catch (std::logic_error const&) 66   2 catch (std::logic_error const&)
67   { 67   {
HITCBC 68   2 throw_logic_error("delay requires an io_context-backed executor"); 68   2 throw_logic_error("delay requires an io_context-backed executor");
HITCBC 69   2 } 69   2 }
MISUBC 70   catch (std::exception const& e) 70   catch (std::exception const& e)
71   { 71   {
MISUBC 72   throw_logic_error(e.what()); 72   throw_logic_error(e.what());
MISUBC 73   } 73   }
HITCBC 74   13100 } 74   13217 }
75   75  
76   } // namespace detail 76   } // namespace detail
77   77  
78   /** IoAwaitable returned by @ref delay. 78   /** IoAwaitable returned by @ref delay.
79   79  
80   Suspends the calling coroutine until the deadline elapses or 80   Suspends the calling coroutine until the deadline elapses or
81   the environment's stop token is activated, whichever comes 81   the environment's stop token is activated, whichever comes
82   first. A deadline already elapsed at suspension, or a stop 82   first. A deadline already elapsed at suspension, or a stop
83   token already active, resumes the coroutine inline, without 83   token already active, resumes the coroutine inline, without
84   starting a timer (see Cancellation below). Otherwise the 84   starting a timer (see Cancellation below). Otherwise the
85   coroutine resumes through the executor once the timer fires 85   coroutine resumes through the executor once the timer fires
86   or a mid-wait cancellation arrives. 86   or a mid-wait cancellation arrives.
87   87  
88   Not intended to be named directly; use the @ref delay factory 88   Not intended to be named directly; use the @ref delay factory
89   overloads instead. 89   overloads instead.
90   90  
91   @par Preconditions 91   @par Preconditions
92   The awaiting coroutine's executor must belong to an 92   The awaiting coroutine's executor must belong to an
93   `io_context`. Any other execution context terminates with a 93   `io_context`. Any other execution context terminates with a
94   diagnostic, because silently running without a timer would 94   diagnostic, because silently running without a timer would
95   drop the requested delay. 95   drop the requested delay.
96   96  
97   @par Cancellation 97   @par Cancellation
98   If stop is already requested before suspension, the coroutine 98   If stop is already requested before suspension, the coroutine
99   resumes immediately with `error::canceled`. If stop is 99   resumes immediately with `error::canceled`. If stop is
100   requested while suspended, the pending wait is cancelled and 100   requested while suspended, the pending wait is cancelled and
101   the coroutine resumes with `error::canceled`. Requesting stop 101   the coroutine resumes with `error::canceled`. Requesting stop
102   from another thread while the io_context runs in 102   from another thread while the io_context runs in
103   single_threaded mode (auto-enabled at concurrency_hint == 1) 103   single_threaded mode (auto-enabled at concurrency_hint == 1)
104   is not permitted by io_context's threading rules; 104   is not permitted by io_context's threading rules;
105   cross-thread cancellation requires a multi-threaded-capable 105   cross-thread cancellation requires a multi-threaded-capable
106   context. 106   context.
107   107  
108   @see delay 108   @see delay
109   */ 109   */
110   class delay_awaitable 110   class delay_awaitable
111   { 111   {
112   // wait() names timer's private awaitable type; decltype is 112   // wait() names timer's private awaitable type; decltype is
113   // the only way to store it here. 113   // the only way to store it here.
114   using wait_type = decltype(std::declval<detail::timer&>().wait()); 114   using wait_type = decltype(std::declval<detail::timer&>().wait());
115   115  
116   std::chrono::steady_clock::time_point deadline_{}; 116   std::chrono::steady_clock::time_point deadline_{};
117   std::chrono::nanoseconds dur_{}; 117   std::chrono::nanoseconds dur_{};
118   bool has_deadline_ = false; 118   bool has_deadline_ = false;
119   bool canceled_ = false; 119   bool canceled_ = false;
120   std::optional<detail::timer> timer_; 120   std::optional<detail::timer> timer_;
121   std::optional<wait_type> wait_; 121   std::optional<wait_type> wait_;
122   122  
123   public: 123   public:
124   /// Construct an awaitable that waits for `dur` nanoseconds. 124   /// Construct an awaitable that waits for `dur` nanoseconds.
HITCBC 125   16525 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur) 125   16553 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur)
126   { 126   {
HITCBC 127   16525 } 127   16553 }
128   128  
129   /// Construct an awaitable that waits until `tp`. 129   /// Construct an awaitable that waits until `tp`.
HITCBC 130   16 explicit delay_awaitable(std::chrono::steady_clock::time_point tp) noexcept 130   16 explicit delay_awaitable(std::chrono::steady_clock::time_point tp) noexcept
HITCBC 131   16 : deadline_(tp) 131   16 : deadline_(tp)
HITCBC 132   16 , has_deadline_(true) 132   16 , has_deadline_(true)
133   { 133   {
HITCBC 134   16 } 134   16 }
135   135  
136   /// Construct by transferring state from `other`. 136   /// Construct by transferring state from `other`.
137   // Only moved before await_suspend; wait_ is engaged after. 137   // Only moved before await_suspend; wait_ is engaged after.
HITCBC 138   18569 delay_awaitable(delay_awaitable&&) = default; 138   18597 delay_awaitable(delay_awaitable&&) = default;
139   139  
140   delay_awaitable(delay_awaitable const&) = delete; 140   delay_awaitable(delay_awaitable const&) = delete;
141   delay_awaitable& operator=(delay_awaitable const&) = delete; 141   delay_awaitable& operator=(delay_awaitable const&) = delete;
142   delay_awaitable& operator=(delay_awaitable&&) = delete; 142   delay_awaitable& operator=(delay_awaitable&&) = delete;
143   143  
144   /// Return false unconditionally; see await_suspend. 144   /// Return false unconditionally; see await_suspend.
145   // The elapsed-deadline fast path must run after the stop-token 145   // The elapsed-deadline fast path must run after the stop-token
146   // check, and only await_suspend receives the env carrying it. 146   // check, and only await_suspend receives the env carrying it.
HITCBC 147   16539 bool await_ready() const noexcept 147   16567 bool await_ready() const noexcept
148   { 148   {
HITCBC 149   16539 return false; 149   16567 return false;
150   } 150   }
151   151  
152   /// Resume inline if stopped or elapsed; else wait on a timer. 152   /// Resume inline if stopped or elapsed; else wait on a timer.
153   std::coroutine_handle<> 153   std::coroutine_handle<>
HITCBC 154   16541 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 154   16569 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
155   { 155   {
HITCBC 156   16541 if (env->stop_token.stop_requested()) 156   16569 if (env->stop_token.stop_requested())
157   { 157   {
HITCBC 158   3509 canceled_ = true; 158   3374 canceled_ = true;
HITCBC 159   3509 return h; 159   3374 return h;
160   } 160   }
161   161  
162   // Elapsed deadlines complete synchronously, but only once a 162   // Elapsed deadlines complete synchronously, but only once a
163   // pending stop request has already been ruled out above. 163   // pending stop request has already been ruled out above.
HITCBC 164   26050 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now() 164   26376 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now()
HITCBC 165   13018 : dur_.count() <= 0) 165   13181 : dur_.count() <= 0)
HITCBC 166   177 return h; 166   224 return h;
167   167  
HITCBC 168   12855 detail::emplace_delay_timer(timer_, env->executor.context()); 168   12971 detail::emplace_delay_timer(timer_, env->executor.context());
169   169  
HITCBC 170   12853 if (has_deadline_) 170   12969 if (has_deadline_)
HITCBC 171   12 timer_->expires_at(deadline_); 171   12 timer_->expires_at(deadline_);
172   else 172   else
HITCBC 173   12841 timer_->expires_after(dur_); 173   12957 timer_->expires_after(dur_);
174   174  
HITCBC 175   12853 wait_.emplace(timer_->wait()); 175   12969 wait_.emplace(timer_->wait());
HITCBC 176   12853 return wait_->await_suspend(h, env); 176   12969 return wait_->await_suspend(h, env);
177   } 177   }
178   178  
179   /// Return empty on expiry, `error::canceled` if stop won. 179   /// Return empty on expiry, `error::canceled` if stop won.
HITCBC 180   16514 [[nodiscard]] capy::io_result<> await_resume() noexcept 180   16542 [[nodiscard]] capy::io_result<> await_resume() noexcept
181   { 181   {
HITCBC 182   16514 if (canceled_) 182   16542 if (canceled_)
HITCBC 183   3509 return {capy::error::canceled}; 183   3374 return {capy::error::canceled};
HITCBC 184   13005 if (wait_) 184   13168 if (wait_)
HITCBC 185   12828 return wait_->await_resume(); 185   12944 return wait_->await_resume();
HITCBC 186   177 return {}; 186   224 return {};
187   } 187   }
188   }; 188   };
189   189  
190   /** IoAwaitable returned by the clock overloads of @ref delay. 190   /** IoAwaitable returned by the clock overloads of @ref delay.
191   191  
192   Suspends the calling coroutine until `Clock::now()` reaches the 192   Suspends the calling coroutine until `Clock::now()` reaches the
193   deadline or the environment's stop token is activated. The wait 193   deadline or the environment's stop token is activated. The wait
194   is a sequence of steady-clock timer waits: after each expiry the 194   is a sequence of steady-clock timer waits: after each expiry the
195   clock is re-read and, if the deadline is unreached, the same 195   clock is re-read and, if the deadline is unreached, the same
196   frame-embedded waiter is re-published for the next 196   frame-embedded waiter is re-published for the next
197   `Traits::to_wait_duration` cap — without resuming the coroutine 197   `Traits::to_wait_duration` cap — without resuming the coroutine
198   and without allocating. 198   and without allocating.
199   199  
200   Not intended to be named directly; use the @ref delay factory 200   Not intended to be named directly; use the @ref delay factory
201   overloads instead. 201   overloads instead.
202   202  
203   @par Preconditions 203   @par Preconditions
204   The awaiting coroutine's executor must belong to an 204   The awaiting coroutine's executor must belong to an
205   `io_context`. Any other execution context terminates with a 205   `io_context`. Any other execution context terminates with a
206   diagnostic, because silently running without a timer would 206   diagnostic, because silently running without a timer would
207   drop the requested delay. 207   drop the requested delay.
208   208  
209   @par Cancellation 209   @par Cancellation
210   Identical to @ref delay_awaitable: stop already requested 210   Identical to @ref delay_awaitable: stop already requested
211   resumes inline with `error::canceled`; stop while suspended 211   resumes inline with `error::canceled`; stop while suspended
212   cancels the pending wait, including between re-arms. 212   cancels the pending wait, including between re-arms.
213   213  
214   @see delay, wait_traits 214   @see delay, wait_traits
215   */ 215   */
216   template<class Clock, class Traits> 216   template<class Clock, class Traits>
217   class clock_delay_awaitable 217   class clock_delay_awaitable
218   { 218   {
219   typename Clock::time_point deadline_{}; 219   typename Clock::time_point deadline_{};
220   bool canceled_ = false; 220   bool canceled_ = false;
221   std::optional<detail::timer> timer_; 221   std::optional<detail::timer> timer_;
222   detail::waiter_node w_; 222   detail::waiter_node w_;
223   223  
224   std::chrono::nanoseconds 224   std::chrono::nanoseconds
HITCBC 225   4495 next_wait(typename Clock::time_point now) const noexcept 225   4327 next_wait(typename Clock::time_point now) const noexcept
226   { 226   {
HITCBC 227   4495 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now)); 227   4327 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now));
228   } 228   }
229   229  
230   // Runs on the scheduler thread executing the completion op, 230   // Runs on the scheduler thread executing the completion op,
231   // before the continuation is posted, so the frame cannot die 231   // before the continuation is posted, so the frame cannot die
232   // concurrently. 232   // concurrently.
HITCBC 233   4493 static bool on_fire(void* ctx) noexcept 233   4325 static bool on_fire(void* ctx) noexcept
234   { 234   {
HITCBC 235   4493 auto* self = static_cast<clock_delay_awaitable*>(ctx); 235   4325 auto* self = static_cast<clock_delay_awaitable*>(ctx);
236   // Canceled: resume and surface the error 236   // Canceled: resume and surface the error
HITCBC 237   4493 if (self->w_.ec_) 237   4325 if (self->w_.ec_)
HITCBC 238   2 return false; 238   3 return false;
HITCBC 239   4491 auto now = Clock::now(); 239   4322 auto now = Clock::now();
HITCBC 240   4491 if (now >= self->deadline_) 240   4322 if (now >= self->deadline_)
HITCBC 241   243 return false; 241   243 return false;
242   // Re-publish and return without touching the node again: 242   // Re-publish and return without touching the node again:
243   // the wait may complete on another thread immediately after. 243   // the wait may complete on another thread immediately after.
HITCBC 244   4248 if (self->timer_->rearm_wait(self->w_, self->next_wait(now))) 244   4079 if (self->timer_->rearm_wait(self->w_, self->next_wait(now)))
HITCBC 245   4248 return true; 245   4079 return true;
246   // Heap growth failed; finish the wait with an error rather 246   // Heap growth failed; finish the wait with an error rather
247   // than strand the frame with an unbalanced work count. 247   // than strand the frame with an unbalanced work count.
MISUBC 248   self->w_.ec_ = std::make_error_code(std::errc::not_enough_memory); 248   self->w_.ec_ = std::make_error_code(std::errc::not_enough_memory);
MISUBC 249   return false; 249   return false;
250   } 250   }
251   251  
252   public: 252   public:
253   /// Construct an awaitable that waits until `tp` on `Clock`. 253   /// Construct an awaitable that waits until `tp` on `Clock`.
HITCBC 254   1253 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept 254   1253 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept
HITCBC 255   1253 : deadline_(tp) 255   1253 : deadline_(tp)
256   { 256   {
HITCBC 257   1253 } 257   1253 }
258   258  
259   /// Construct by transferring the deadline from `other`. 259   /// Construct by transferring the deadline from `other`.
260   // Only moved before await_suspend; w_ is quiescent until then. 260   // Only moved before await_suspend; w_ is quiescent until then.
HITCBC 261   1253 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept 261   1253 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept
HITCBC 262   1253 : deadline_(other.deadline_) 262   1253 : deadline_(other.deadline_)
263   { 263   {
HITCBC 264   1253 } 264   1253 }
265   265  
266   clock_delay_awaitable(clock_delay_awaitable const&) = delete; 266   clock_delay_awaitable(clock_delay_awaitable const&) = delete;
267   clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete; 267   clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete;
268   clock_delay_awaitable& operator=(clock_delay_awaitable&&) = delete; 268   clock_delay_awaitable& operator=(clock_delay_awaitable&&) = delete;
269   269  
270   /// Return false unconditionally; see await_suspend. 270   /// Return false unconditionally; see await_suspend.
271   // The elapsed-deadline fast path must run after the stop-token 271   // The elapsed-deadline fast path must run after the stop-token
272   // check, and only await_suspend receives the env carrying it. 272   // check, and only await_suspend receives the env carrying it.
HITCBC 273   1253 bool await_ready() const noexcept 273   1253 bool await_ready() const noexcept
274   { 274   {
HITCBC 275   1253 return false; 275   1253 return false;
276   } 276   }
277   277  
278   /// Resume inline if stopped or reached; else wait on a timer. 278   /// Resume inline if stopped or reached; else wait on a timer.
279   std::coroutine_handle<> 279   std::coroutine_handle<>
HITCBC 280   1253 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 280   1253 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
281   { 281   {
HITCBC 282   1253 if (env->stop_token.stop_requested()) 282   1253 if (env->stop_token.stop_requested())
283   { 283   {
HITCBC 284   1004 canceled_ = true; 284   1003 canceled_ = true;
HITCBC 285   1004 return h; 285   1003 return h;
286   } 286   }
287   287  
HITCBC 288   249 auto now = Clock::now(); 288   250 auto now = Clock::now();
HITCBC 289   249 if (now >= deadline_) 289   250 if (now >= deadline_)
HITCBC 290   2 return h; 290   2 return h;
291   291  
HITCBC 292   247 detail::emplace_delay_timer(timer_, env->executor.context()); 292   248 detail::emplace_delay_timer(timer_, env->executor.context());
293   293  
HITCBC 294   247 timer_->expires_after(next_wait(now)); 294   248 timer_->expires_after(next_wait(now));
295   295  
HITCBC 296   247 w_.bind(h, *env); 296   248 w_.bind(h, *env);
HITCBC 297   247 w_.on_fire_ = &on_fire; 297   248 w_.on_fire_ = &on_fire;
HITCBC 298   247 w_.on_fire_ctx_ = this; 298   248 w_.on_fire_ctx_ = this;
299   // Never the elapsed fast path: a capped expiry that elapses 299   // Never the elapsed fast path: a capped expiry that elapses
300   // before publication must still reach on_fire, not complete 300   // before publication must still reach on_fire, not complete
301   // the clock wait early. 301   // the clock wait early.
HITCBC 302   247 return timer_->publish_wait(w_); 302   248 return timer_->publish_wait(w_);
303   } 303   }
304   304  
305   /// Return empty on deadline, `error::canceled` if stop won. 305   /// Return empty on deadline, `error::canceled` if stop won.
HITCBC 306   1251 [[nodiscard]] capy::io_result<> await_resume() noexcept 306   1251 [[nodiscard]] capy::io_result<> await_resume() noexcept
307   { 307   {
HITCBC 308   1251 if (canceled_) 308   1251 if (canceled_)
HITCBC 309   1004 return {capy::error::canceled}; 309   1003 return {capy::error::canceled};
HITCBC 310   247 if (timer_) 310   248 if (timer_)
HITCBC 311   245 return {w_.ec_}; 311   246 return {w_.ec_};
HITCBC 312   2 return {}; 312   2 return {};
313   } 313   }
314   }; 314   };
315   315  
316   /** Suspend the current coroutine for a duration. 316   /** Suspend the current coroutine for a duration.
317   317  
318   Returns an IoAwaitable that completes at or after the 318   Returns an IoAwaitable that completes at or after the
319   specified duration, or earlier if the environment's stop 319   specified duration, or earlier if the environment's stop
320   token is activated. Zero or negative durations complete 320   token is activated. Zero or negative durations complete
321   synchronously. 321   synchronously.
322   322  
323   @par Example 323   @par Example
324   @par !example duration 324   @par !example duration
325   325  
326   @param dur The duration to wait. 326   @param dur The duration to wait.
327   327  
328   @return A @ref delay_awaitable yielding `io_result<>`. 328   @return A @ref delay_awaitable yielding `io_result<>`.
329   */ 329   */
330   template<typename Rep, typename Period> 330   template<typename Rep, typename Period>
331   [[nodiscard]] delay_awaitable 331   [[nodiscard]] delay_awaitable
HITCBC 332   16523 delay(std::chrono::duration<Rep, Period> dur) noexcept 332   16551 delay(std::chrono::duration<Rep, Period> dur) noexcept
333   { 333   {
HITCBC 334   16523 return delay_awaitable(detail::clamp_to_ns(dur)); 334   16551 return delay_awaitable(detail::clamp_to_ns(dur));
335   } 335   }
336   336  
337   /** Suspend the current coroutine until a time point. 337   /** Suspend the current coroutine until a time point.
338   338  
339   Returns an IoAwaitable that completes at or after `tp`, or 339   Returns an IoAwaitable that completes at or after `tp`, or
340   earlier if the environment's stop token is activated. Time 340   earlier if the environment's stop token is activated. Time
341   points already reached complete synchronously. 341   points already reached complete synchronously.
342   342  
343   @param tp The steady-clock time point to wait until. 343   @param tp The steady-clock time point to wait until.
344   344  
345   @return A @ref delay_awaitable yielding `io_result<>`. 345   @return A @ref delay_awaitable yielding `io_result<>`.
346   */ 346   */
347   [[nodiscard]] inline delay_awaitable 347   [[nodiscard]] inline delay_awaitable
HITCBC 348   16 delay(std::chrono::steady_clock::time_point tp) noexcept 348   16 delay(std::chrono::steady_clock::time_point tp) noexcept
349   { 349   {
HITCBC 350   16 return delay_awaitable(tp); 350   16 return delay_awaitable(tp);
351   } 351   }
352   352  
353   /** Suspend the current coroutine until a time point on `Clock`. 353   /** Suspend the current coroutine until a time point on `Clock`.
354   354  
355   Returns an IoAwaitable that completes at or after the first 355   Returns an IoAwaitable that completes at or after the first
356   observation of `Clock::now() >= tp`, or earlier if the 356   observation of `Clock::now() >= tp`, or earlier if the
357   environment's stop token is activated. The wait is one or more 357   environment's stop token is activated. The wait is one or more
358   bounded steady-clock waits, re-reading `Clock::now()` after 358   bounded steady-clock waits, re-reading `Clock::now()` after
359   each; `Traits::to_wait_duration` bounds each one. With the 359   each; `Traits::to_wait_duration` bounds each one. With the
360   default @ref wait_traits a single full-length wait is used, so 360   default @ref wait_traits a single full-length wait is used, so
361   an adjustment of `Clock` mid-wait is observed only at natural 361   an adjustment of `Clock` mid-wait is observed only at natural
362   wakeup; supply capping traits to bound that latency. Time 362   wakeup; supply capping traits to bound that latency. Time
363   points already reached complete synchronously. 363   points already reached complete synchronously.
364   364  
365   @note `Clock::now()` and `Traits::to_wait_duration` are invoked 365   @note `Clock::now()` and `Traits::to_wait_duration` are invoked
366   on the io_context's run thread and must not throw or block. 366   on the io_context's run thread and must not throw or block.
367   367  
368   @par Example 368   @par Example
369   @par !example system_clock_deadline 369   @par !example system_clock_deadline
370   370  
371   @tparam Traits The wait-traits policy; `void` selects 371   @tparam Traits The wait-traits policy; `void` selects
372   @ref wait_traits. 372   @ref wait_traits.
373   373  
374   @param tp The time point to wait until. 374   @param tp The time point to wait until.
375   375  
376   @return A @ref clock_delay_awaitable yielding `io_result<>`. 376   @return A @ref clock_delay_awaitable yielding `io_result<>`.
377   */ 377   */
378   template<class Traits = void, class Clock, class Duration> 378   template<class Traits = void, class Clock, class Duration>
379   requires(!std::same_as<Clock, std::chrono::steady_clock>) && 379   requires(!std::same_as<Clock, std::chrono::steady_clock>) &&
380   (std::is_void_v<Traits> || WaitTraits<Traits, Clock>) 380   (std::is_void_v<Traits> || WaitTraits<Traits, Clock>)
381   [[nodiscard]] auto 381   [[nodiscard]] auto
HITCBC 382   1253 delay(std::chrono::time_point<Clock, Duration> tp) noexcept 382   1253 delay(std::chrono::time_point<Clock, Duration> tp) noexcept
383   { 383   {
384   using traits_type = 384   using traits_type =
385   std::conditional_t<std::is_void_v<Traits>, wait_traits<Clock>, Traits>; 385   std::conditional_t<std::is_void_v<Traits>, wait_traits<Clock>, Traits>;
386   // ceil preserves completes-at-or-after when Duration is coarser 386   // ceil preserves completes-at-or-after when Duration is coarser
387   // than the clock's native duration 387   // than the clock's native duration
388   return clock_delay_awaitable<Clock, traits_type>( 388   return clock_delay_awaitable<Clock, traits_type>(
HITCBC 389   1253 std::chrono::ceil<typename Clock::duration>(tp)); 389   1253 std::chrono::ceil<typename Clock::duration>(tp));
390   } 390   }
391   391  
392   } // namespace boost::corosio 392   } // namespace boost::corosio
393   393  
394   #endif 394   #endif