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   11883 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 38   12704 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   21682 return dsec(dur) >= dsec((nanoseconds::max)()) 49   23312 return dsec(dur) >= dsec((nanoseconds::max)())
HITCBC 50   21682 ? (nanoseconds::max)() 50   23312 ? (nanoseconds::max)()
HITCBC 51   23760 : dsec(dur) <= dsec((nanoseconds::min)()) 51   25402 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 52   11879 ? (nanoseconds::min)() 52   12700 ? (nanoseconds::min)()
HITCBC 53   11881 : duration_cast<nanoseconds>(dur); 53   12702 : 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   7870 emplace_delay_timer( 60   8691 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   7870 t.emplace(ctx); 65   8691 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   7868 } 76   8689 }
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   11861 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept 127   12680 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
HITCBC 128   11861 : dur_(dur) 128   12680 : dur_(dur)
129   { 129   {
HITCBC 130   11861 } 130   12680 }
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   13905 delay_awaitable(delay_awaitable&&) = default; 142   14724 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   11875 bool await_ready() const noexcept 151   12694 bool await_ready() const noexcept
152   { 152   {
HITCBC 153   11875 return false; 153   12694 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   11877 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 158   12696 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
159   { 159   {
HITCBC 160   11877 if(env->stop_token.stop_requested()) 160   12696 if(env->stop_token.stop_requested())
161   { 161   {
HITCBC 162   4011 canceled_ = true; 162   4009 canceled_ = true;
HITCBC 163   4011 return h; 163   4009 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   15718 if(has_deadline_ ? 168   17360 if(has_deadline_ ?
HITCBC 169   7866 deadline_ <= std::chrono::steady_clock::now() : 169   8687 deadline_ <= std::chrono::steady_clock::now() :
HITCBC 170   7852 dur_.count() <= 0) 170   8673 dur_.count() <= 0)
HITCBC 171   8 return h; 171   10 return h;
172   172  
HITCBC 173   7858 detail::emplace_delay_timer(timer_, env->executor.context()); 173   8677 detail::emplace_delay_timer(timer_, env->executor.context());
174   174  
HITCBC 175   7856 if(has_deadline_) 175   8675 if(has_deadline_)
HITCBC 176   12 timer_->expires_at(deadline_); 176   12 timer_->expires_at(deadline_);
177   else 177   else
HITCBC 178   7844 timer_->expires_after(dur_); 178   8663 timer_->expires_after(dur_);
179   179  
HITCBC 180   7856 wait_.emplace(timer_->wait()); 180   8675 wait_.emplace(timer_->wait());
HITCBC 181   7856 return wait_->await_suspend(h, env); 181   8675 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   11851 capy::io_result<> await_resume() noexcept 185   12670 capy::io_result<> await_resume() noexcept
186   { 186   {
HITCBC 187   11851 if(canceled_) 187   12670 if(canceled_)
HITCBC 188   4011 return {capy::error::canceled}; 188   4009 return {capy::error::canceled};
HITCBC 189   7840 if(wait_) 189   8661 if(wait_)
HITCBC 190   7832 return wait_->await_resume(); 190   8651 return wait_->await_resume();
HITCBC 191   8 return {}; 191   10 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 capy::io_result<> await_resume() noexcept 315   1014 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   11859 delay(std::chrono::duration<Rep, Period> dur) noexcept 343   12678 delay(std::chrono::duration<Rep, Period> dur) noexcept
344   { 344   {
HITCBC 345   11859 return delay_awaitable(detail::clamp_to_ns(dur)); 345   12678 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