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   20917 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 39   21107 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 - 35443 return dsec(dur) >= dsec((nanoseconds::max)()) 50 + 21105 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)()
DCB 51 - 35443 ? (nanoseconds::max)()  
HITCBC 52   41828 : dsec(dur) <= dsec((nanoseconds::min)()) 51   42208 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 53 - 20913 ? (nanoseconds::min)() 52 + 21103 ? (nanoseconds::min)()
HITCBC 54 - 20915 : duration_cast<nanoseconds>(dur); 53 + 21105 : duration_cast<nanoseconds>(dur);
55   } 54   }
56   55  
57   // A non-io_context executor cannot supply a timer service, and 56   // A non-io_context executor cannot supply a timer service, and
58   // await_suspend is driven through a noexcept wrapper, so translate 57   // await_suspend is driven through a noexcept wrapper, so translate
59   // the service-lookup failure into a clear terminate. 58   // the service-lookup failure into a clear terminate.
60   inline void 59   inline void
HITCBC 61 - 12974 emplace_delay_timer( 60 + 13005 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx)
62 - std::optional<timer>& t, capy::execution_context& ctx)  
63   { 61   {
64   try 62   try
65   { 63   {
HITCBC 66   12974 t.emplace(ctx); 64   13005 t.emplace(ctx);
67   } 65   }
HITCBC 68 - 2 catch(std::logic_error const&) 66 + 2 catch (std::logic_error const&)
69   { 67   {
HITCBC 70 - 2 throw_logic_error( 68 + 2 throw_logic_error("delay requires an io_context-backed executor");
71 - "delay requires an io_context-backed executor");  
HITCBC 72   2 } 69   2 }
MISUBC 73 - catch(std::exception const& e) 70 + catch (std::exception const& e)
74   { 71   {
MISUBC 75   throw_logic_error(e.what()); 72   throw_logic_error(e.what());
MISUBC 76   } 73   }
HITCBC 77   12972 } 74   13003 }
78   75  
79   } // namespace detail 76   } // namespace detail
80   77  
81   /** IoAwaitable returned by @ref delay. 78   /** IoAwaitable returned by @ref delay.
82   79  
83   Suspends the calling coroutine until the deadline elapses or 80   Suspends the calling coroutine until the deadline elapses or
84   the environment's stop token is activated, whichever comes 81   the environment's stop token is activated, whichever comes
85   first. A deadline already elapsed at suspension, or a stop 82   first. A deadline already elapsed at suspension, or a stop
86   token already active, resumes the coroutine inline, without 83   token already active, resumes the coroutine inline, without
87   starting a timer (see Cancellation below). Otherwise the 84   starting a timer (see Cancellation below). Otherwise the
88   coroutine resumes through the executor once the timer fires 85   coroutine resumes through the executor once the timer fires
89   or a mid-wait cancellation arrives. 86   or a mid-wait cancellation arrives.
90   87  
91   Not intended to be named directly; use the @ref delay factory 88   Not intended to be named directly; use the @ref delay factory
92   overloads instead. 89   overloads instead.
93   90  
94   @par Preconditions 91   @par Preconditions
95   The awaiting coroutine's executor must belong to an 92   The awaiting coroutine's executor must belong to an
96   `io_context`. Any other execution context terminates with a 93   `io_context`. Any other execution context terminates with a
97   diagnostic, because silently running without a timer would 94   diagnostic, because silently running without a timer would
98   drop the requested delay. 95   drop the requested delay.
99   96  
100   @par Cancellation 97   @par Cancellation
101   If stop is already requested before suspension, the coroutine 98   If stop is already requested before suspension, the coroutine
102   resumes immediately with `error::canceled`. If stop is 99   resumes immediately with `error::canceled`. If stop is
103   requested while suspended, the pending wait is cancelled and 100   requested while suspended, the pending wait is cancelled and
104   the coroutine resumes with `error::canceled`. Requesting stop 101   the coroutine resumes with `error::canceled`. Requesting stop
105   from another thread while the io_context runs in 102   from another thread while the io_context runs in
106   single_threaded mode (auto-enabled at concurrency_hint == 1) 103   single_threaded mode (auto-enabled at concurrency_hint == 1)
107   is not permitted by io_context's threading rules; 104   is not permitted by io_context's threading rules;
108   cross-thread cancellation requires a multi-threaded-capable 105   cross-thread cancellation requires a multi-threaded-capable
109   context. 106   context.
110   107  
111   @see delay 108   @see delay
112   */ 109   */
113   class delay_awaitable 110   class delay_awaitable
114   { 111   {
115   // wait() names timer's private awaitable type; decltype is 112   // wait() names timer's private awaitable type; decltype is
116   // the only way to store it here. 113   // the only way to store it here.
117   using wait_type = decltype(std::declval<detail::timer&>().wait()); 114   using wait_type = decltype(std::declval<detail::timer&>().wait());
118   115  
119   std::chrono::steady_clock::time_point deadline_{}; 116   std::chrono::steady_clock::time_point deadline_{};
120   std::chrono::nanoseconds dur_{}; 117   std::chrono::nanoseconds dur_{};
121   bool has_deadline_ = false; 118   bool has_deadline_ = false;
122 - bool canceled_ = false; 119 + bool canceled_ = false;
123   std::optional<detail::timer> timer_; 120   std::optional<detail::timer> timer_;
124   std::optional<wait_type> wait_; 121   std::optional<wait_type> wait_;
125   122  
126   public: 123   public:
127   /// Construct an awaitable that waits for `dur` nanoseconds. 124   /// Construct an awaitable that waits for `dur` nanoseconds.
HITCBC 128 - 16598 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept 125 + 16620 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur)
DCB 129 - 16598 : dur_(dur)  
130   { 126   {
HITCBC 131   16598 } 127   16620 }
132   128  
133   /// Construct an awaitable that waits until `tp`. 129   /// Construct an awaitable that waits until `tp`.
HITCBC 134 - 16 explicit delay_awaitable( 130 + 16 explicit delay_awaitable(std::chrono::steady_clock::time_point tp) noexcept
135 - std::chrono::steady_clock::time_point tp) noexcept  
HITCBC 136   16 : deadline_(tp) 131   16 : deadline_(tp)
HITCBC 137   16 , has_deadline_(true) 132   16 , has_deadline_(true)
138   { 133   {
HITCBC 139   16 } 134   16 }
140   135  
141   /// Construct by transferring state from `other`. 136   /// Construct by transferring state from `other`.
142   // Only moved before await_suspend; wait_ is engaged after. 137   // Only moved before await_suspend; wait_ is engaged after.
HITCBC 143   18642 delay_awaitable(delay_awaitable&&) = default; 138   18664 delay_awaitable(delay_awaitable&&) = default;
144   139  
145 - delay_awaitable(delay_awaitable const&) = delete; 140 + delay_awaitable(delay_awaitable const&) = delete;
146   delay_awaitable& operator=(delay_awaitable const&) = delete; 141   delay_awaitable& operator=(delay_awaitable const&) = delete;
147 - delay_awaitable& operator=(delay_awaitable&&) = delete; 142 + delay_awaitable& operator=(delay_awaitable&&) = delete;
148   143  
149   /// Return false unconditionally; see await_suspend. 144   /// Return false unconditionally; see await_suspend.
150   // The elapsed-deadline fast path must run after the stop-token 145   // The elapsed-deadline fast path must run after the stop-token
151   // check, and only await_suspend receives the env carrying it. 146   // check, and only await_suspend receives the env carrying it.
HITCBC 152   16612 bool await_ready() const noexcept 147   16634 bool await_ready() const noexcept
153   { 148   {
HITCBC 154   16612 return false; 149   16634 return false;
155   } 150   }
156   151  
157   /// Resume inline if stopped or elapsed; else wait on a timer. 152   /// Resume inline if stopped or elapsed; else wait on a timer.
158   std::coroutine_handle<> 153   std::coroutine_handle<>
HITCBC 159   16614 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 154   16636 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
160   { 155   {
HITCBC 161 - 16614 if(env->stop_token.stop_requested()) 156 + 16636 if (env->stop_token.stop_requested())
162   { 157   {
HITCBC 163   3809 canceled_ = true; 158   3801 canceled_ = true;
HITCBC 164   3809 return h; 159   3801 return h;
165   } 160   }
166   161  
167   // Elapsed deadlines complete synchronously, but only once a 162   // Elapsed deadlines complete synchronously, but only once a
168   // pending stop request has already been ruled out above. 163   // pending stop request has already been ruled out above.
HITCBC 169 - 25596 if(has_deadline_ ? 164 + 25656 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now()
HITCBC 170 - 12805 deadline_ <= std::chrono::steady_clock::now() : 165 + 12821 : dur_.count() <= 0)
DCB 171 - 12791 dur_.count() <= 0)  
HITCBC 172   78 return h; 166   78 return h;
173   167  
HITCBC 174   12727 detail::emplace_delay_timer(timer_, env->executor.context()); 168   12757 detail::emplace_delay_timer(timer_, env->executor.context());
175   169  
HITCBC 176 - 12725 if(has_deadline_) 170 + 12755 if (has_deadline_)
HITCBC 177   12 timer_->expires_at(deadline_); 171   12 timer_->expires_at(deadline_);
178   else 172   else
HITCBC 179   12713 timer_->expires_after(dur_); 173   12743 timer_->expires_after(dur_);
180   174  
HITCBC 181   12725 wait_.emplace(timer_->wait()); 175   12755 wait_.emplace(timer_->wait());
HITCBC 182   12725 return wait_->await_suspend(h, env); 176   12755 return wait_->await_suspend(h, env);
183   } 177   }
184   178  
185   /// Return empty on expiry, `error::canceled` if stop won. 179   /// Return empty on expiry, `error::canceled` if stop won.
HITCBC 186   16587 [[nodiscard]] capy::io_result<> await_resume() noexcept 180   16609 [[nodiscard]] capy::io_result<> await_resume() noexcept
187   { 181   {
HITCBC 188 - 16587 if(canceled_) 182 + 16609 if (canceled_)
HITCBC 189   3809 return {capy::error::canceled}; 183   3801 return {capy::error::canceled};
HITCBC 190 - 12778 if(wait_) 184 + 12808 if (wait_)
HITCBC 191   12700 return wait_->await_resume(); 185   12730 return wait_->await_resume();
HITCBC 192   78 return {}; 186   78 return {};
193   } 187   }
194   }; 188   };
195   189  
196   /** IoAwaitable returned by the clock overloads of @ref delay. 190   /** IoAwaitable returned by the clock overloads of @ref delay.
197   191  
198   Suspends the calling coroutine until `Clock::now()` reaches the 192   Suspends the calling coroutine until `Clock::now()` reaches the
199   deadline or the environment's stop token is activated. The wait 193   deadline or the environment's stop token is activated. The wait
200   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
201   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
202   frame-embedded waiter is re-published for the next 196   frame-embedded waiter is re-published for the next
203   `Traits::to_wait_duration` cap — without resuming the coroutine 197   `Traits::to_wait_duration` cap — without resuming the coroutine
204   and without allocating. 198   and without allocating.
205   199  
206   Not intended to be named directly; use the @ref delay factory 200   Not intended to be named directly; use the @ref delay factory
207   overloads instead. 201   overloads instead.
208   202  
209   @par Preconditions 203   @par Preconditions
210   The awaiting coroutine's executor must belong to an 204   The awaiting coroutine's executor must belong to an
211   `io_context`. Any other execution context terminates with a 205   `io_context`. Any other execution context terminates with a
212   diagnostic, because silently running without a timer would 206   diagnostic, because silently running without a timer would
213   drop the requested delay. 207   drop the requested delay.
214   208  
215   @par Cancellation 209   @par Cancellation
216   Identical to @ref delay_awaitable: stop already requested 210   Identical to @ref delay_awaitable: stop already requested
217   resumes inline with `error::canceled`; stop while suspended 211   resumes inline with `error::canceled`; stop while suspended
218   cancels the pending wait, including between re-arms. 212   cancels the pending wait, including between re-arms.
219   213  
220   @see delay, wait_traits 214   @see delay, wait_traits
221   */ 215   */
222   template<class Clock, class Traits> 216   template<class Clock, class Traits>
223   class clock_delay_awaitable 217   class clock_delay_awaitable
224   { 218   {
225   typename Clock::time_point deadline_{}; 219   typename Clock::time_point deadline_{};
226   bool canceled_ = false; 220   bool canceled_ = false;
227   std::optional<detail::timer> timer_; 221   std::optional<detail::timer> timer_;
228   detail::waiter_node w_; 222   detail::waiter_node w_;
229   223  
230   std::chrono::nanoseconds 224   std::chrono::nanoseconds
HITCBC 231   4321 next_wait(typename Clock::time_point now) const noexcept 225   4489 next_wait(typename Clock::time_point now) const noexcept
232   { 226   {
HITCBC 233 - 4321 return detail::clamp_to_ns( 227 + 4489 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now));
DCB 234 - 8642 Traits::to_wait_duration(deadline_ - now));  
235   } 228   }
236   229  
237   // Runs on the scheduler thread executing the completion op, 230   // Runs on the scheduler thread executing the completion op,
238   // before the continuation is posted, so the frame cannot die 231   // before the continuation is posted, so the frame cannot die
239   // concurrently. 232   // concurrently.
HITCBC 240   4319 static bool on_fire(void* ctx) noexcept 233   4487 static bool on_fire(void* ctx) noexcept
241   { 234   {
HITCBC 242   4319 auto* self = static_cast<clock_delay_awaitable*>(ctx); 235   4487 auto* self = static_cast<clock_delay_awaitable*>(ctx);
243   // Canceled: resume and surface the error 236   // Canceled: resume and surface the error
HITCBC 244 - 4319 if(self->w_.ec_) 237 + 4487 if (self->w_.ec_)
HITCBC 245   2 return false; 238   3 return false;
HITCBC 246   4317 auto now = Clock::now(); 239   4484 auto now = Clock::now();
HITCBC 247 - 4317 if(now >= self->deadline_) 240 + 4484 if (now >= self->deadline_)
HITCBC 248   243 return false; 241   243 return false;
249   // Re-publish and return without touching the node again: 242   // Re-publish and return without touching the node again:
250   // the wait may complete on another thread immediately after. 243   // the wait may complete on another thread immediately after.
HITCBC 251 - 4074 if(self->timer_->rearm_wait(self->w_, self->next_wait(now))) 244 + 4241 if (self->timer_->rearm_wait(self->w_, self->next_wait(now)))
HITCBC 252   4074 return true; 245   4241 return true;
253   // Heap growth failed; finish the wait with an error rather 246   // Heap growth failed; finish the wait with an error rather
254   // than strand the frame with an unbalanced work count. 247   // than strand the frame with an unbalanced work count.
MISUBC 255   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 256   return false; 249   return false;
257   } 250   }
258   251  
259   public: 252   public:
260   /// Construct an awaitable that waits until `tp` on `Clock`. 253   /// Construct an awaitable that waits until `tp` on `Clock`.
HITCBC 261 - 1253 explicit clock_delay_awaitable( 254 + 1253 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept
262 - typename Clock::time_point tp) noexcept  
HITCBC 263   1253 : deadline_(tp) 255   1253 : deadline_(tp)
264   { 256   {
HITCBC 265   1253 } 257   1253 }
266   258  
267   /// Construct by transferring the deadline from `other`. 259   /// Construct by transferring the deadline from `other`.
268   // Only moved before await_suspend; w_ is quiescent until then. 260   // Only moved before await_suspend; w_ is quiescent until then.
HITCBC 269   1253 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept 261   1253 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept
HITCBC 270   1253 : deadline_(other.deadline_) 262   1253 : deadline_(other.deadline_)
271   { 263   {
HITCBC 272   1253 } 264   1253 }
273   265  
274 - clock_delay_awaitable(clock_delay_awaitable const&) = delete; 266 + clock_delay_awaitable(clock_delay_awaitable const&) = delete;
275 - clock_delay_awaitable& 267 + clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete;
276 - operator=(clock_delay_awaitable const&) = delete; 268 + clock_delay_awaitable& operator=(clock_delay_awaitable&&) = delete;
277 - clock_delay_awaitable&  
278 - operator=(clock_delay_awaitable&&) = delete;  
279   269  
280   /// Return false unconditionally; see await_suspend. 270   /// Return false unconditionally; see await_suspend.
281   // The elapsed-deadline fast path must run after the stop-token 271   // The elapsed-deadline fast path must run after the stop-token
282   // check, and only await_suspend receives the env carrying it. 272   // check, and only await_suspend receives the env carrying it.
HITCBC 283   1253 bool await_ready() const noexcept 273   1253 bool await_ready() const noexcept
284   { 274   {
HITCBC 285   1253 return false; 275   1253 return false;
286   } 276   }
287   277  
288   /// Resume inline if stopped or reached; else wait on a timer. 278   /// Resume inline if stopped or reached; else wait on a timer.
289   std::coroutine_handle<> 279   std::coroutine_handle<>
HITCBC 290   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)
291   { 281   {
HITCBC 292 - 1253 if(env->stop_token.stop_requested()) 282 + 1253 if (env->stop_token.stop_requested())
293   { 283   {
HITCBC 294   1004 canceled_ = true; 284   1003 canceled_ = true;
HITCBC 295   1004 return h; 285   1003 return h;
296   } 286   }
297   287  
HITCBC 298   249 auto now = Clock::now(); 288   250 auto now = Clock::now();
HITCBC 299 - 249 if(now >= deadline_) 289 + 250 if (now >= deadline_)
HITCBC 300   2 return h; 290   2 return h;
301   291  
HITCBC 302   247 detail::emplace_delay_timer(timer_, env->executor.context()); 292   248 detail::emplace_delay_timer(timer_, env->executor.context());
303   293  
HITCBC 304   247 timer_->expires_after(next_wait(now)); 294   248 timer_->expires_after(next_wait(now));
305   295  
HITCBC 306   247 w_.bind(h, *env); 296   248 w_.bind(h, *env);
HITCBC 307   247 w_.on_fire_ = &on_fire; 297   248 w_.on_fire_ = &on_fire;
HITCBC 308   247 w_.on_fire_ctx_ = this; 298   248 w_.on_fire_ctx_ = this;
309   // Never the elapsed fast path: a capped expiry that elapses 299   // Never the elapsed fast path: a capped expiry that elapses
310   // before publication must still reach on_fire, not complete 300   // before publication must still reach on_fire, not complete
311   // the clock wait early. 301   // the clock wait early.
HITCBC 312   247 return timer_->publish_wait(w_); 302   248 return timer_->publish_wait(w_);
313   } 303   }
314   304  
315   /// Return empty on deadline, `error::canceled` if stop won. 305   /// Return empty on deadline, `error::canceled` if stop won.
HITCBC 316   1251 [[nodiscard]] capy::io_result<> await_resume() noexcept 306   1251 [[nodiscard]] capy::io_result<> await_resume() noexcept
317   { 307   {
HITCBC 318 - 1251 if(canceled_) 308 + 1251 if (canceled_)
HITCBC 319   1004 return {capy::error::canceled}; 309   1003 return {capy::error::canceled};
HITCBC 320 - 247 if(timer_) 310 + 248 if (timer_)
HITCBC 321   245 return {w_.ec_}; 311   246 return {w_.ec_};
HITCBC 322   2 return {}; 312   2 return {};
323   } 313   }
324   }; 314   };
325   315  
326   /** Suspend the current coroutine for a duration. 316   /** Suspend the current coroutine for a duration.
327   317  
328   Returns an IoAwaitable that completes at or after the 318   Returns an IoAwaitable that completes at or after the
329   specified duration, or earlier if the environment's stop 319   specified duration, or earlier if the environment's stop
330   token is activated. Zero or negative durations complete 320   token is activated. Zero or negative durations complete
331   synchronously. 321   synchronously.
332   322  
333   @par Example 323   @par Example
334   @par !example duration 324   @par !example duration
335   325  
336   @param dur The duration to wait. 326   @param dur The duration to wait.
337   327  
338   @return A @ref delay_awaitable yielding `io_result<>`. 328   @return A @ref delay_awaitable yielding `io_result<>`.
339   */ 329   */
340   template<typename Rep, typename Period> 330   template<typename Rep, typename Period>
341   [[nodiscard]] delay_awaitable 331   [[nodiscard]] delay_awaitable
HITCBC 342   16596 delay(std::chrono::duration<Rep, Period> dur) noexcept 332   16618 delay(std::chrono::duration<Rep, Period> dur) noexcept
343   { 333   {
HITCBC 344   16596 return delay_awaitable(detail::clamp_to_ns(dur)); 334   16618 return delay_awaitable(detail::clamp_to_ns(dur));
345   } 335   }
346   336  
347   /** Suspend the current coroutine until a time point. 337   /** Suspend the current coroutine until a time point.
348   338  
349   Returns an IoAwaitable that completes at or after `tp`, or 339   Returns an IoAwaitable that completes at or after `tp`, or
350   earlier if the environment's stop token is activated. Time 340   earlier if the environment's stop token is activated. Time
351   points already reached complete synchronously. 341   points already reached complete synchronously.
352   342  
353   @param tp The steady-clock time point to wait until. 343   @param tp The steady-clock time point to wait until.
354   344  
355   @return A @ref delay_awaitable yielding `io_result<>`. 345   @return A @ref delay_awaitable yielding `io_result<>`.
356   */ 346   */
357   [[nodiscard]] inline delay_awaitable 347   [[nodiscard]] inline delay_awaitable
HITCBC 358   16 delay(std::chrono::steady_clock::time_point tp) noexcept 348   16 delay(std::chrono::steady_clock::time_point tp) noexcept
359   { 349   {
HITCBC 360   16 return delay_awaitable(tp); 350   16 return delay_awaitable(tp);
361   } 351   }
362   352  
363   /** Suspend the current coroutine until a time point on `Clock`. 353   /** Suspend the current coroutine until a time point on `Clock`.
364   354  
365   Returns an IoAwaitable that completes at or after the first 355   Returns an IoAwaitable that completes at or after the first
366   observation of `Clock::now() >= tp`, or earlier if the 356   observation of `Clock::now() >= tp`, or earlier if the
367   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
368   bounded steady-clock waits, re-reading `Clock::now()` after 358   bounded steady-clock waits, re-reading `Clock::now()` after
369   each; `Traits::to_wait_duration` bounds each one. With the 359   each; `Traits::to_wait_duration` bounds each one. With the
370   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
371   an adjustment of `Clock` mid-wait is observed only at natural 361   an adjustment of `Clock` mid-wait is observed only at natural
372   wakeup; supply capping traits to bound that latency. Time 362   wakeup; supply capping traits to bound that latency. Time
373   points already reached complete synchronously. 363   points already reached complete synchronously.
374   364  
375   @note `Clock::now()` and `Traits::to_wait_duration` are invoked 365   @note `Clock::now()` and `Traits::to_wait_duration` are invoked
376   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.
377   367  
378   @par Example 368   @par Example
379   @par !example system_clock_deadline 369   @par !example system_clock_deadline
380   370  
381   @tparam Traits The wait-traits policy; `void` selects 371   @tparam Traits The wait-traits policy; `void` selects
382   @ref wait_traits. 372   @ref wait_traits.
383   373  
384   @param tp The time point to wait until. 374   @param tp The time point to wait until.
385   375  
386   @return A @ref clock_delay_awaitable yielding `io_result<>`. 376   @return A @ref clock_delay_awaitable yielding `io_result<>`.
387   */ 377   */
388   template<class Traits = void, class Clock, class Duration> 378   template<class Traits = void, class Clock, class Duration>
389 - requires (!std::same_as<Clock, std::chrono::steady_clock>) && 379 + requires(!std::same_as<Clock, std::chrono::steady_clock>) &&
390 - (std::is_void_v<Traits> || WaitTraits<Traits, Clock>) 380 + (std::is_void_v<Traits> || WaitTraits<Traits, Clock>)
391   [[nodiscard]] auto 381   [[nodiscard]] auto
HITCBC 392   1253 delay(std::chrono::time_point<Clock, Duration> tp) noexcept 382   1253 delay(std::chrono::time_point<Clock, Duration> tp) noexcept
393   { 383   {
394 - using traits_type = std::conditional_t< 384 + using traits_type =
395 - std::is_void_v<Traits>, wait_traits<Clock>, Traits>; 385 + std::conditional_t<std::is_void_v<Traits>, wait_traits<Clock>, Traits>;
396   // ceil preserves completes-at-or-after when Duration is coarser 386   // ceil preserves completes-at-or-after when Duration is coarser
397   // than the clock's native duration 387   // than the clock's native duration
398   return clock_delay_awaitable<Clock, traits_type>( 388   return clock_delay_awaitable<Clock, traits_type>(
HITCBC 399   1253 std::chrono::ceil<typename Clock::duration>(tp)); 389   1253 std::chrono::ceil<typename Clock::duration>(tp));
400   } 390   }
401   391  
402   } // namespace boost::corosio 392   } // namespace boost::corosio
403   393  
404   #endif 394   #endif