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