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