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   20504 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 39   19441 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   20502 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)() 50   19439 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)()
HITCBC 51   41002 : dsec(dur) <= dsec((nanoseconds::min)()) 51   38876 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 52   20500 ? (nanoseconds::min)() 52   19437 ? (nanoseconds::min)()
HITCBC 53   20502 : duration_cast<nanoseconds>(dur); 53   19439 : 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   12861 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx) 60   11198 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx)
61   { 61   {
62   try 62   try
63   { 63   {
HITCBC 64   12861 t.emplace(ctx); 64   11198 t.emplace(ctx);
65   } 65   }
HITCBC 66   2 catch (std::logic_error const&) 66   2 catch (std::logic_error const&)
67   { 67   {
HITCBC 68   2 throw_logic_error("delay requires an io_context-backed executor"); 68   2 throw_logic_error("delay requires an io_context-backed executor");
HITCBC 69   2 } 69   2 }
MISUBC 70   ✗ catch (std::exception const& e) 70   ✗ catch (std::exception const& e)
71   { 71   {
MISUBC 72   ✗ throw_logic_error(e.what()); 72   ✗ throw_logic_error(e.what());
MISUBC 73   ✗ } 73   ✗ }
HITCBC 74   12859 } 74   11196 }
75   75  
76   } // namespace detail 76   } // namespace detail
77   77  
78 - /** IoAwaitable returned by @ref delay. 78 + /** Suspends the calling coroutine until the deadline elapses or
79 -  
80 - Suspends the calling coroutine until the deadline elapses or  
81   the environment's stop token is activated, whichever comes 79   the environment's stop token is activated, whichever comes
82   first. A deadline already elapsed at suspension, or a stop 80   first. A deadline already elapsed at suspension, or a stop
83   token already active, resumes the coroutine inline, without 81   token already active, resumes the coroutine inline, without
84   starting a timer (see Cancellation below). Otherwise the 82   starting a timer (see Cancellation below). Otherwise the
85   coroutine resumes through the executor once the timer fires 83   coroutine resumes through the executor once the timer fires
86   or a mid-wait cancellation arrives. 84   or a mid-wait cancellation arrives.
87   85  
88 - Not intended to be named directly; use the @ref delay factory 86 + Not intended to be named directly. Use the @ref delay factory
89   overloads instead. 87   overloads instead.
90   88  
91 - @par Preconditions 89 + @pre The awaiting coroutine's executor must belong to an
92 - The awaiting coroutine's executor must belong to an 90 + `io_context`. Any other execution context terminates with a
93 - `io_context`. Any other execution context terminates with a 91 + diagnostic, because silently running without a timer would
94 - diagnostic, because silently running without a timer would 92 + drop the requested delay.
95 - drop the requested delay.  
96   93  
97   @par Cancellation 94   @par Cancellation
98   If stop is already requested before suspension, the coroutine 95   If stop is already requested before suspension, the coroutine
99   resumes immediately with `error::canceled`. If stop is 96   resumes immediately with `error::canceled`. If stop is
100   requested while suspended, the pending wait is cancelled and 97   requested while suspended, the pending wait is cancelled and
101 - the coroutine resumes with `error::canceled`. Requesting stop 98 + the coroutine resumes with `error::canceled`. Requesting stop from
102 - from another thread while the io_context runs in 99 + another thread while the `io_context` runs in `single_threaded` mode is
103 - single_threaded mode (auto-enabled at concurrency_hint == 1) 100 + not permitted by `io_context`'s threading rules. That mode is
104 - is not permitted by io_context's threading rules; 101 + auto-enabled at `concurrency_hint` == 1. Cross-thread cancellation
105 - cross-thread cancellation requires a multi-threaded-capable 102 + requires a multi-threaded-capable context.
106 - context.  
107   103  
108   @see delay 104   @see delay
109   */ 105   */
110   class delay_awaitable 106   class delay_awaitable
111   { 107   {
112   // wait() names timer's private awaitable type; decltype is 108   // wait() names timer's private awaitable type; decltype is
113   // the only way to store it here. 109   // the only way to store it here.
114   using wait_type = decltype(std::declval<detail::timer&>().wait()); 110   using wait_type = decltype(std::declval<detail::timer&>().wait());
115   111  
116   std::chrono::steady_clock::time_point deadline_{}; 112   std::chrono::steady_clock::time_point deadline_{};
117   std::chrono::nanoseconds dur_{}; 113   std::chrono::nanoseconds dur_{};
118   bool has_deadline_ = false; 114   bool has_deadline_ = false;
119   bool canceled_ = false; 115   bool canceled_ = false;
120   std::optional<detail::timer> timer_; 116   std::optional<detail::timer> timer_;
121   std::optional<wait_type> wait_; 117   std::optional<wait_type> wait_;
122   118  
123   public: 119   public:
124   /// Construct an awaitable that waits for `dur` nanoseconds. 120   /// Construct an awaitable that waits for `dur` nanoseconds.
HITCBC 125   16357 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur) 121   14807 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur)
126   { 122   {
HITCBC 127   16357 } 123   14807 }
128   124  
129   /// Construct an awaitable that waits until `tp`. 125   /// Construct an awaitable that waits until `tp`.
HITCBC 130   16 explicit delay_awaitable(std::chrono::steady_clock::time_point tp) noexcept 126   16 explicit delay_awaitable(std::chrono::steady_clock::time_point tp) noexcept
HITCBC 131   16 : deadline_(tp) 127   16 : deadline_(tp)
HITCBC 132   16 , has_deadline_(true) 128   16 , has_deadline_(true)
133   { 129   {
HITCBC 134   16 } 130   16 }
135 - /// Construct by transferring state from `other`.  
136   131  
137   // Only moved before await_suspend; wait_ is engaged after. 132   // Only moved before await_suspend; wait_ is engaged after.
  133 + /// Construct by transferring state from `other`.
HITCBC 138   18401 delay_awaitable(delay_awaitable&&) = default; 134   16851 delay_awaitable(delay_awaitable&&) = default;
139   135  
140 - delay_awaitable(delay_awaitable const&) = delete; 136 + /// Copy construction is disabled; an awaitable owns its timer.
  137 + delay_awaitable(delay_awaitable const&) = delete;
  138 + /// Copy assignment is disabled; an awaitable owns its timer.
141   delay_awaitable& operator=(delay_awaitable const&) = delete; 139   delay_awaitable& operator=(delay_awaitable const&) = delete;
142 - delay_awaitable& operator=(delay_awaitable&&) = delete; 140 + /// Move assignment is disabled; an awaitable is moved only before it is awaited.
  141 + delay_awaitable& operator=(delay_awaitable&&) = delete;
143   142  
144   /// Return false unconditionally; see await_suspend. 143   /// Return false unconditionally; see await_suspend.
145   // The elapsed-deadline fast path must run after the stop-token 144   // The elapsed-deadline fast path must run after the stop-token
146   // check, and only await_suspend receives the env carrying it. 145   // check, and only await_suspend receives the env carrying it.
HITCBC 147   16371 bool await_ready() const noexcept 146   14821 bool await_ready() const noexcept
148   { 147   {
HITCBC 149   16371 return false; 148   14821 return false;
150   } 149   }
151   150  
152 - /// Resume inline if stopped or elapsed; else wait on a timer. 151 + /** Resume inline if stopped or elapsed; else wait on a timer.
  152 +
  153 + @param h Coroutine handle to resume on completion.
  154 + @param env The I/O environment, carrying the executor, stop token
  155 + and frame allocator.
  156 +
  157 + @return The handle to resume immediately, or `noop_coroutine()` when
  158 + the wait was published to the timer service.
  159 + */
153   std::coroutine_handle<> 160   std::coroutine_handle<>
HITCBC 154   16373 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 161   14823 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
155   { 162   {
HITCBC 156   16373 if (env->stop_token.stop_requested()) 163   14823 if (env->stop_token.stop_requested())
157   { 164   {
HITCBC 158   3619 canceled_ = true; 165   3789 canceled_ = true;
HITCBC 159   3619 return h; 166   3789 return h;
160   } 167   }
161   168  
162   // Elapsed deadlines complete synchronously, but only once a 169   // Elapsed deadlines complete synchronously, but only once a
163   // pending stop request has already been ruled out above. 170   // pending stop request has already been ruled out above.
HITCBC 164   25494 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now() 171   22054 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now()
HITCBC 165   12740 : dur_.count() <= 0) 172   11020 : dur_.count() <= 0)
HITCBC 166   142 return h; 173   83 return h;
167   174  
HITCBC 168   12612 detail::emplace_delay_timer(timer_, env->executor.context()); 175   10951 detail::emplace_delay_timer(timer_, env->executor.context());
169   176  
HITCBC 170   12610 if (has_deadline_) 177   10949 if (has_deadline_)
HITCBC 171   12 timer_->expires_at(deadline_); 178   12 timer_->expires_at(deadline_);
172   else 179   else
HITCBC 173   12598 timer_->expires_after(dur_); 180   10937 timer_->expires_after(dur_);
174   181  
HITCBC 175   12610 wait_.emplace(timer_->wait()); 182   10949 wait_.emplace(timer_->wait());
HITCBC 176   12610 return wait_->await_suspend(h, env); 183   10949 return wait_->await_suspend(h, env);
177   } 184   }
178   185  
179   /// Return empty on expiry, `error::canceled` if stop won. 186   /// Return empty on expiry, `error::canceled` if stop won.
HITCBC 180   16346 [[nodiscard]] capy::io_result<> await_resume() noexcept 187   14796 [[nodiscard]] capy::io_result<> await_resume() noexcept
181   { 188   {
HITCBC 182   16346 if (canceled_) 189   14796 if (canceled_)
HITCBC 183   3619 return {capy::error::canceled}; 190   3789 return {capy::error::canceled};
HITCBC 184   12727 if (wait_) 191   11007 if (wait_)
HITCBC 185   12585 return wait_->await_resume(); 192   10924 return wait_->await_resume();
HITCBC 186   142 return {}; 193   83 return {};
187   } 194   }
188   }; 195   };
189   196  
190 - /** IoAwaitable returned by the clock overloads of @ref delay. 197 + /** Suspends the calling coroutine until `Clock::now()` reaches the
191 - 198 + deadline or the environment's stop token is activated. The wait is a
192 - Suspends the calling coroutine until `Clock::now()` reaches the 199 + sequence of steady-clock timer waits. After each expiry the clock is
193 - deadline or the environment's stop token is activated. The wait 200 + re-read. If the deadline is unreached, the same frame-embedded
194 - is a sequence of steady-clock timer waits: after each expiry the 201 + waiter is re-published for the next `Traits::to_wait_duration` cap.
195 - clock is re-read and, if the deadline is unreached, the same 202 + That re-publish neither resumes the coroutine nor allocates.
196 - frame-embedded waiter is re-published for the next  
197 - `Traits::to_wait_duration` cap — without resuming the coroutine  
198 - and without allocating.  
199   203  
200 - Not intended to be named directly; use the @ref delay factory 204 + Not intended to be named directly. Use the @ref delay factory
201   overloads instead. 205   overloads instead.
202   206  
203 - @par Preconditions 207 + @tparam Clock The clock the deadline is expressed in.
204 - The awaiting coroutine's executor must belong to an 208 + @tparam Traits The wait-traits policy bounding each steady-clock wait.
205 - `io_context`. Any other execution context terminates with a 209 +
206 - diagnostic, because silently running without a timer would 210 + @pre The awaiting coroutine's executor must belong to an
207 - drop the requested delay. 211 + `io_context`. Any other execution context terminates with a
  212 + diagnostic, because silently running without a timer would
  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   4149 next_wait(typename Clock::time_point now) const noexcept 231   4636 next_wait(typename Clock::time_point now) const noexcept
226   { 232   {
HITCBC 227   4149 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now)); 233   4636 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now));
228   } 234   }
229   235  
230   // Runs on the scheduler thread executing the completion op, 236   // Runs on the scheduler thread executing the completion op,
231   // before the continuation is posted, so the frame cannot die 237   // before the continuation is posted, so the frame cannot die
232   // concurrently. 238   // concurrently.
HITCBC 233   4147 static bool on_fire(void* ctx) noexcept 239   4634 static bool on_fire(void* ctx) noexcept
234   { 240   {
HITCBC 235   4147 auto* self = static_cast<clock_delay_awaitable*>(ctx); 241   4634 auto* self = static_cast<clock_delay_awaitable*>(ctx);
236   // Canceled: resume and surface the error 242   // Canceled: resume and surface the error
HITCBC 237   4147 if (self->w_.ec_) 243   4634 if (self->w_.ec_)
HITCBC 238   4 return false; 244   2 return false;
HITCBC 239   4143 auto now = Clock::now(); 245   4632 auto now = Clock::now();
HITCBC 240   4143 if (now >= self->deadline_) 246   4632 if (now >= self->deadline_)
HITCBC 241   243 return false; 247   243 return false;
242   // Re-publish and return without touching the node again: 248   // Re-publish and return without touching the node again:
243   // the wait may complete on another thread immediately after. 249   // the wait may complete on another thread immediately after.
HITCBC 244   3900 if (self->timer_->rearm_wait(self->w_, self->next_wait(now))) 250   4389 if (self->timer_->rearm_wait(self->w_, self->next_wait(now)))
HITCBC 245   3900 return true; 251   4389 return true;
246   // Heap growth failed; finish the wait with an error rather 252   // Heap growth failed; finish the wait with an error rather
247   // than strand the frame with an unbalanced work count. 253   // than strand the frame with an unbalanced work count.
MISUBC 248   ✗ 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 249   ✗ return false; 255   ✗ return false;
250   } 256   }
251   257  
252   public: 258   public:
253   /// Construct an awaitable that waits until `tp` on `Clock`. 259   /// Construct an awaitable that waits until `tp` on `Clock`.
HITCBC 254   1253 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept 260   1253 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept
HITCBC 255   1253 : deadline_(tp) 261   1253 : deadline_(tp)
256   { 262   {
HITCBC 257   1253 } 263   1253 }
258 - /// Construct by transferring the deadline from `other`.  
259   264  
260   // Only moved before await_suspend; w_ is quiescent until then. 265   // Only moved before await_suspend; w_ is quiescent until then.
  266 + /// Construct by transferring the deadline from `other`.
HITCBC 261   1253 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept 267   1253 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept
HITCBC 262   1253 : deadline_(other.deadline_) 268   1253 : deadline_(other.deadline_)
263   { 269   {
HITCBC 264   1253 } 270   1253 }
265   271  
266 - clock_delay_awaitable(clock_delay_awaitable const&) = delete; 272 + /// Copy construction is disabled; an awaitable owns its timer.
  273 + clock_delay_awaitable(clock_delay_awaitable const&) = delete;
  274 + /// Copy assignment is disabled; an awaitable owns its timer.
267   clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete; 275   clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete;
268 - clock_delay_awaitable& operator=(clock_delay_awaitable&&) = delete; 276 + /// Move assignment is disabled; an awaitable is moved only before it is awaited.
  277 + clock_delay_awaitable& operator=(clock_delay_awaitable&&) = delete;
269   278  
270   /// Return false unconditionally; see await_suspend. 279   /// Return false unconditionally; see await_suspend.
271   // The elapsed-deadline fast path must run after the stop-token 280   // The elapsed-deadline fast path must run after the stop-token
272   // check, and only await_suspend receives the env carrying it. 281   // check, and only await_suspend receives the env carrying it.
HITCBC 273   1253 bool await_ready() const noexcept 282   1253 bool await_ready() const noexcept
274   { 283   {
HITCBC 275   1253 return false; 284   1253 return false;
276   } 285   }
277   286  
278 - /// Resume inline if stopped or reached; else wait on a timer. 287 + /** Resume inline if stopped or reached; else wait on a timer.
  288 +
  289 + @param h Coroutine handle to resume on completion.
  290 + @param env The I/O environment, carrying the executor, stop token
  291 + and frame allocator.
  292 +
  293 + @return The handle to resume immediately, or `noop_coroutine()` when
  294 + the wait was published to the timer service.
  295 + */
279   std::coroutine_handle<> 296   std::coroutine_handle<>
HITCBC 280   1253 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 297   1253 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
281   { 298   {
HITCBC 282   1253 if (env->stop_token.stop_requested()) 299   1253 if (env->stop_token.stop_requested())
283   { 300   {
HITCBC 284   1002 canceled_ = true; 301   1004 canceled_ = true;
HITCBC 285   1002 return h; 302   1004 return h;
286   } 303   }
287   304  
HITCBC 288   251 auto now = Clock::now(); 305   249 auto now = Clock::now();
HITCBC 289   251 if (now >= deadline_) 306   249 if (now >= deadline_)
HITCBC 290   2 return h; 307   2 return h;
291   308  
HITCBC 292   249 detail::emplace_delay_timer(timer_, env->executor.context()); 309   247 detail::emplace_delay_timer(timer_, env->executor.context());
293   310  
HITCBC 294   249 timer_->expires_after(next_wait(now)); 311   247 timer_->expires_after(next_wait(now));
295   312  
HITCBC 296   249 w_.bind(h, *env); 313   247 w_.bind(h, *env);
HITCBC 297   249 w_.on_fire_ = &on_fire; 314   247 w_.on_fire_ = &on_fire;
HITCBC 298   249 w_.on_fire_ctx_ = this; 315   247 w_.on_fire_ctx_ = this;
299   // Never the elapsed fast path: a capped expiry that elapses 316   // Never the elapsed fast path: a capped expiry that elapses
300   // before publication must still reach on_fire, not complete 317   // before publication must still reach on_fire, not complete
301   // the clock wait early. 318   // the clock wait early.
HITCBC 302   249 return timer_->publish_wait(w_); 319   247 return timer_->publish_wait(w_);
303   } 320   }
304   321  
305   /// Return empty on deadline, `error::canceled` if stop won. 322   /// Return empty on deadline, `error::canceled` if stop won.
HITCBC 306   1251 [[nodiscard]] capy::io_result<> await_resume() noexcept 323   1251 [[nodiscard]] capy::io_result<> await_resume() noexcept
307   { 324   {
HITCBC 308   1251 if (canceled_) 325   1251 if (canceled_)
HITCBC 309   1002 return {capy::error::canceled}; 326   1004 return {capy::error::canceled};
HITCBC 310   249 if (timer_) 327   247 if (timer_)
HITCBC 311   247 return {w_.ec_}; 328   245 return {w_.ec_};
HITCBC 312   2 return {}; 329   2 return {};
313   } 330   }
314   }; 331   };
315   332  
316   /** Suspend the current coroutine for a duration. 333   /** Suspend the current coroutine for a duration.
317   334  
318   Returns an IoAwaitable that completes at or after the 335   Returns an IoAwaitable that completes at or after the
319   specified duration, or earlier if the environment's stop 336   specified duration, or earlier if the environment's stop
320   token is activated. Zero or negative durations complete 337   token is activated. Zero or negative durations complete
321   synchronously. 338   synchronously.
322   339  
323   @par Example 340   @par Example
324   @par !example duration 341   @par !example duration
325   342  
326   @param dur The duration to wait. 343   @param dur The duration to wait.
327   344  
328   @return A @ref delay_awaitable yielding `io_result<>`. 345   @return A @ref delay_awaitable yielding `io_result<>`.
329   */ 346   */
330   template<typename Rep, typename Period> 347   template<typename Rep, typename Period>
331   [[nodiscard]] delay_awaitable 348   [[nodiscard]] delay_awaitable
HITCBC 332   16355 delay(std::chrono::duration<Rep, Period> dur) noexcept 349   14805 delay(std::chrono::duration<Rep, Period> dur) noexcept
333   { 350   {
HITCBC 334   16355 return delay_awaitable(detail::clamp_to_ns(dur)); 351   14805 return delay_awaitable(detail::clamp_to_ns(dur));
335   } 352   }
336   353  
337   /** Suspend the current coroutine until a time point. 354   /** Suspend the current coroutine until a time point.
338   355  
339   Returns an IoAwaitable that completes at or after `tp`, or 356   Returns an IoAwaitable that completes at or after `tp`, or
340   earlier if the environment's stop token is activated. Time 357   earlier if the environment's stop token is activated. Time
341   points already reached complete synchronously. 358   points already reached complete synchronously.
342   359  
343   @param tp The steady-clock time point to wait until. 360   @param tp The steady-clock time point to wait until.
344   361  
345   @return A @ref delay_awaitable yielding `io_result<>`. 362   @return A @ref delay_awaitable yielding `io_result<>`.
346   */ 363   */
347   [[nodiscard]] inline delay_awaitable 364   [[nodiscard]] inline delay_awaitable
HITCBC 348   16 delay(std::chrono::steady_clock::time_point tp) noexcept 365   16 delay(std::chrono::steady_clock::time_point tp) noexcept
349   { 366   {
HITCBC 350   16 return delay_awaitable(tp); 367   16 return delay_awaitable(tp);
351   } 368   }
352   369  
353   /** Suspend the current coroutine until a time point on `Clock`. 370   /** Suspend the current coroutine until a time point on `Clock`.
354   371  
355   Returns an IoAwaitable that completes at or after the first 372   Returns an IoAwaitable that completes at or after the first
356   observation of `Clock::now() >= tp`, or earlier if the 373   observation of `Clock::now() >= tp`, or earlier if the
357   environment's stop token is activated. The wait is one or more 374   environment's stop token is activated. The wait is one or more
358   bounded steady-clock waits, re-reading `Clock::now()` after 375   bounded steady-clock waits, re-reading `Clock::now()` after
359 - each; `Traits::to_wait_duration` bounds each one. With the 376 + each. `Traits::to_wait_duration` bounds each one. With the default
360 - default @ref wait_traits a single full-length wait is used, so 377 + @ref wait_traits a single full-length wait is used. An adjustment of
361 - an adjustment of `Clock` mid-wait is observed only at natural 378 + `Clock` mid-wait is therefore observed only at natural wakeup.
362 - wakeup; supply capping traits to bound that latency. Time 379 + Supply capping traits to bound that latency. Time
363   points already reached complete synchronously. 380   points already reached complete synchronously.
364   381  
365   @note `Clock::now()` and `Traits::to_wait_duration` are invoked 382   @note `Clock::now()` and `Traits::to_wait_duration` are invoked
366 - on the io_context's run thread and must not throw or block. 383 + on the `io_context`'s run thread and must not throw or block.
367   384  
368   @par Example 385   @par Example
369   @par !example system_clock_deadline 386   @par !example system_clock_deadline
370   387  
371   @tparam Traits The wait-traits policy; `void` selects 388   @tparam Traits The wait-traits policy; `void` selects
372   @ref wait_traits. 389   @ref wait_traits.
  390 +
  391 + @tparam Clock The clock type. This overload does not participate
  392 + when `Clock` is `std::chrono::steady_clock`. The dedicated
  393 + @ref delay overload taking a `steady_clock::time_point` handles
  394 + that case.
373   395  
374   @param tp The time point to wait until. 396   @param tp The time point to wait until.
375   397  
376   @return A @ref clock_delay_awaitable yielding `io_result<>`. 398   @return A @ref clock_delay_awaitable yielding `io_result<>`.
377   */ 399   */
378   template<class Traits = void, class Clock, class Duration> 400   template<class Traits = void, class Clock, class Duration>
379   requires(!std::same_as<Clock, std::chrono::steady_clock>) && 401   requires(!std::same_as<Clock, std::chrono::steady_clock>) &&
380   (std::is_void_v<Traits> || WaitTraits<Traits, Clock>) 402   (std::is_void_v<Traits> || WaitTraits<Traits, Clock>)
381   [[nodiscard]] auto 403   [[nodiscard]] auto
HITCBC 382   1253 delay(std::chrono::time_point<Clock, Duration> tp) noexcept 404   1253 delay(std::chrono::time_point<Clock, Duration> tp) noexcept
383   { 405   {
384   using traits_type = 406   using traits_type =
385   std::conditional_t<std::is_void_v<Traits>, wait_traits<Clock>, Traits>; 407   std::conditional_t<std::is_void_v<Traits>, wait_traits<Clock>, Traits>;
386   // ceil preserves completes-at-or-after when Duration is coarser 408   // ceil preserves completes-at-or-after when Duration is coarser
387   // than the clock's native duration 409   // than the clock's native duration
388   return clock_delay_awaitable<Clock, traits_type>( 410   return clock_delay_awaitable<Clock, traits_type>(
HITCBC 389   1253 std::chrono::ceil<typename Clock::duration>(tp)); 411   1253 std::chrono::ceil<typename Clock::duration>(tp));
390   } 412   }
391   413  
392   } // namespace boost::corosio 414   } // namespace boost::corosio
393   415  
394   #endif 416   #endif