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   10444 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 38   11656 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   18700 return dsec(dur) >= dsec((nanoseconds::max)()) 49   21220 return dsec(dur) >= dsec((nanoseconds::max)())
HITCBC 50   18700 ? (nanoseconds::max)() 50   21220 ? (nanoseconds::max)()
HITCBC 51   20882 : dsec(dur) <= dsec((nanoseconds::min)()) 51   23306 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 52   10440 ? (nanoseconds::min)() 52   11652 ? (nanoseconds::min)()
HITCBC 53   10442 : duration_cast<nanoseconds>(dur); 53   11654 : 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   6498 emplace_delay_timer( 60   7649 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   6498 t.emplace(ctx); 65   7649 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   6496 } 76   7647 }
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   10328 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept 127   11636 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
HITCBC 128   10328 : dur_(dur) 128   11636 : dur_(dur)
129   { 129   {
HITCBC 130   10328 } 130   11636 }
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   12372 delay_awaitable(delay_awaitable&&) = default; 142   13680 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   10342 bool await_ready() const noexcept 151   11650 bool await_ready() const noexcept
152   { 152   {
HITCBC 153   10342 return false; 153   11650 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   10344 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 158   11652 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
159   { 159   {
HITCBC 160   10344 if(env->stop_token.stop_requested()) 160   11652 if(env->stop_token.stop_requested())
161   { 161   {
HITCBC 162   3910 canceled_ = true; 162   4002 canceled_ = true;
HITCBC 163   3910 return h; 163   4002 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   12854 if(has_deadline_ ? 168   15286 if(has_deadline_ ?
HITCBC 169   6434 deadline_ <= std::chrono::steady_clock::now() : 169   7650 deadline_ <= std::chrono::steady_clock::now() :
HITCBC 170   6420 dur_.count() <= 0) 170   7636 dur_.count() <= 0)
HITCBC 171   42 return h; 171   11 return h;
172   172  
HITCBC 173   6392 detail::emplace_delay_timer(timer_, env->executor.context()); 173   7639 detail::emplace_delay_timer(timer_, env->executor.context());
174   174  
HITCBC 175   6390 if(has_deadline_) 175   7637 if(has_deadline_)
HITCBC 176   12 timer_->expires_at(deadline_); 176   12 timer_->expires_at(deadline_);
177   else 177   else
HITCBC 178   6378 timer_->expires_after(dur_); 178   7625 timer_->expires_after(dur_);
179   179  
HITCBC 180   6390 wait_.emplace(timer_->wait()); 180   7637 wait_.emplace(timer_->wait());
HITCBC 181   6390 return wait_->await_suspend(h, env); 181   7637 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   10318 [[nodiscard]] capy::io_result<> await_resume() noexcept 185   11626 [[nodiscard]] capy::io_result<> await_resume() noexcept
186   { 186   {
HITCBC 187   10318 if(canceled_) 187   11626 if(canceled_)
HITCBC 188   3910 return {capy::error::canceled}; 188   4002 return {capy::error::canceled};
HITCBC 189   6408 if(wait_) 189   7624 if(wait_)
HITCBC 190   6366 return wait_->await_resume(); 190   7613 return wait_->await_resume();
HITCBC 191   42 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   118 next_wait(typename Clock::time_point now) const noexcept 230   22 next_wait(typename Clock::time_point now) const noexcept
231   { 231   {
HITCBC 232   118 return detail::clamp_to_ns( 232   22 return detail::clamp_to_ns(
HITCBC 233   236 Traits::to_wait_duration(deadline_ - now)); 233   44 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   116 static bool on_fire(void* ctx) noexcept 239   20 static bool on_fire(void* ctx) noexcept
240   { 240   {
HITCBC 241   116 auto* self = static_cast<clock_delay_awaitable*>(ctx); 241   20 auto* self = static_cast<clock_delay_awaitable*>(ctx);
242   // Canceled: resume and surface the error 242   // Canceled: resume and surface the error
HITCBC 243   116 if(self->w_.ec_) 243   20 if(self->w_.ec_)
HITCBC 244   98 return false; 244   2 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   908 canceled_ = true; 293   1004 canceled_ = true;
HITCBC 294   908 return h; 294   1004 return h;
295   } 295   }
296   296  
HITCBC 297   108 auto now = Clock::now(); 297   12 auto now = Clock::now();
HITCBC 298   108 if(now >= deadline_) 298   12 if(now >= deadline_)
HITCBC 299   2 return h; 299   2 return h;
300   300  
HITCBC 301   106 detail::emplace_delay_timer(timer_, env->executor.context()); 301   10 detail::emplace_delay_timer(timer_, env->executor.context());
302   302  
HITCBC 303   106 timer_->expires_after(next_wait(now)); 303   10 timer_->expires_after(next_wait(now));
304   304  
HITCBC 305   106 w_.bind(h, *env); 305   10 w_.bind(h, *env);
HITCBC 306   106 w_.on_fire_ = &on_fire; 306   10 w_.on_fire_ = &on_fire;
HITCBC 307   106 w_.on_fire_ctx_ = this; 307   10 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   106 return timer_->publish_wait(w_); 311   10 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   908 return {capy::error::canceled}; 318   1004 return {capy::error::canceled};
HITCBC 319   106 if(timer_) 319   10 if(timer_)
HITCBC 320   104 return {w_.ec_}; 320   8 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   10326 delay(std::chrono::duration<Rep, Period> dur) noexcept 343   11634 delay(std::chrono::duration<Rep, Period> dur) noexcept
344   { 344   {
HITCBC 345   10326 return delay_awaitable(detail::clamp_to_ns(dur)); 345   11634 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