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