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