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/await_context.hpp> 19   #include <boost/capy/ex/await_context.hpp>
20   #include <boost/capy/results.hpp> 20   #include <boost/capy/results.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   27905 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 39   26341 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   27903 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)() 50   26339 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)()
HITCBC 51   55804 : dsec(dur) <= dsec((nanoseconds::min)()) 51   52676 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 52   27901 ? (nanoseconds::min)() 52   26337 ? (nanoseconds::min)()
HITCBC 53   27903 : duration_cast<nanoseconds>(dur); 53   26339 : 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   18254 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx) 60   17912 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx)
61   { 61   {
62   try 62   try
63   { 63   {
HITCBC 64   18254 t.emplace(ctx); 64   17912 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   18252 } 74   17910 }
75   75  
76   } // namespace detail 76   } // namespace detail
77   77  
78   /** Suspends the calling coroutine until the deadline elapses or 78   /** Suspends the calling coroutine until the deadline elapses or
79   the environment's stop token is activated, whichever comes 79   the environment's stop token is activated, whichever comes
80   first. A deadline already elapsed at suspension, or a stop 80   first. A deadline already elapsed at suspension, or a stop
81   token already active, resumes the coroutine inline, without 81   token already active, resumes the coroutine inline, without
82   starting a timer (see Cancellation below). Otherwise the 82   starting a timer (see Cancellation below). Otherwise the
83   coroutine resumes through the executor once the timer fires 83   coroutine resumes through the executor once the timer fires
84   or a mid-wait cancellation arrives. 84   or a mid-wait cancellation arrives.
85   85  
86   Not intended to be named directly. Use the @ref delay factory 86   Not intended to be named directly. Use the @ref delay factory
87   overloads instead. 87   overloads instead.
88   88  
89   @pre The awaiting coroutine's executor must belong to an 89   @pre The awaiting coroutine's executor must belong to an
90   `io_context`. Any other execution context terminates with a 90   `io_context`. Any other execution context terminates with a
91   diagnostic, because silently running without a timer would 91   diagnostic, because silently running without a timer would
92   drop the requested delay. 92   drop the requested delay.
93   93  
94   @par Cancellation 94   @par Cancellation
95   If stop is already requested before suspension, the coroutine 95   If stop is already requested before suspension, the coroutine
96   resumes immediately with `error::canceled`. If stop is 96   resumes immediately with `error::canceled`. If stop is
97   requested while suspended, the pending wait is cancelled and 97   requested while suspended, the pending wait is cancelled and
98   the coroutine resumes with `error::canceled`. Requesting stop from 98   the coroutine resumes with `error::canceled`. Requesting stop from
99   another thread while the `io_context` runs in `single_threaded` mode is 99   another thread while the `io_context` runs in `single_threaded` mode is
100   not permitted by `io_context`'s threading rules. That mode is 100   not permitted by `io_context`'s threading rules. That mode is
101   auto-enabled at `concurrency_hint` == 1. Cross-thread cancellation 101   auto-enabled at `concurrency_hint` == 1. Cross-thread cancellation
102   requires a multi-threaded-capable context. 102   requires a multi-threaded-capable context.
103   103  
104 - @see @ref delay 104 + @see delay
105   */ 105   */
106   class delay_awaitable 106   class delay_awaitable
107   { 107   {
108   // wait() names timer's private awaitable type; decltype is 108   // wait() names timer's private awaitable type; decltype is
109   // the only way to store it here. 109   // the only way to store it here.
110   using wait_type = decltype(std::declval<detail::timer&>().wait()); 110   using wait_type = decltype(std::declval<detail::timer&>().wait());
111   111  
112   std::chrono::steady_clock::time_point deadline_{}; 112   std::chrono::steady_clock::time_point deadline_{};
113   std::chrono::nanoseconds dur_{}; 113   std::chrono::nanoseconds dur_{};
114   bool has_deadline_ = false; 114   bool has_deadline_ = false;
115   bool canceled_ = false; 115   bool canceled_ = false;
116   std::optional<detail::timer> timer_; 116   std::optional<detail::timer> timer_;
117   std::optional<wait_type> wait_; 117   std::optional<wait_type> wait_;
118   118  
119   public: 119   public:
120   /// Construct an awaitable that waits for `dur` nanoseconds. 120   /// Construct an awaitable that waits for `dur` nanoseconds.
HITCBC 121   20994 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur) 121   20796 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur)
122   { 122   {
HITCBC 123   20994 } 123   20796 }
124   124  
125   /// Construct an awaitable that waits until `tp`. 125   /// Construct an awaitable that waits until `tp`.
HITCBC 126   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 127   16 : deadline_(tp) 127   16 : deadline_(tp)
HITCBC 128   16 , has_deadline_(true) 128   16 , has_deadline_(true)
129   { 129   {
HITCBC 130   16 } 130   16 }
131   131  
132   // 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`. 133   /// Construct by transferring state from `other`.
HITCBC 134   23040 delay_awaitable(delay_awaitable&&) = default; 134   22842 delay_awaitable(delay_awaitable&&) = default;
135   135  
136   /// Copy construction is disabled; an awaitable owns its timer. 136   /// Copy construction is disabled; an awaitable owns its timer.
137   delay_awaitable(delay_awaitable const&) = delete; 137   delay_awaitable(delay_awaitable const&) = delete;
138   /// Copy assignment is disabled; an awaitable owns its timer. 138   /// Copy assignment is disabled; an awaitable owns its timer.
139   delay_awaitable& operator=(delay_awaitable const&) = delete; 139   delay_awaitable& operator=(delay_awaitable const&) = delete;
140   /// Move assignment is disabled; an awaitable is moved only before it is awaited. 140   /// Move assignment is disabled; an awaitable is moved only before it is awaited.
141   delay_awaitable& operator=(delay_awaitable&&) = delete; 141   delay_awaitable& operator=(delay_awaitable&&) = delete;
142   142  
143   /// Return false unconditionally; see await_suspend. 143   /// Return false unconditionally; see await_suspend.
144   // The elapsed-deadline fast path must run after the stop-token 144   // The elapsed-deadline fast path must run after the stop-token
145   // check, and only await_suspend receives the env carrying it. 145   // check, and only await_suspend receives the env carrying it.
HITCBC 146   21008 bool await_ready() const noexcept 146   20810 bool await_ready() const noexcept
147   { 147   {
HITCBC 148   21008 return false; 148   20810 return false;
149   } 149   }
150   150  
151   /** Resume inline if stopped or elapsed; else wait on a timer. 151   /** Resume inline if stopped or elapsed; else wait on a timer.
152   152  
153   @param h Coroutine handle to resume on completion. 153   @param h Coroutine handle to resume on completion.
154   @param env The I/O environment, carrying the executor, stop token 154   @param env The I/O environment, carrying the executor, stop token
155   and frame allocator. 155   and frame allocator.
156   156  
157   @return The handle to resume immediately, or `noop_coroutine()` when 157   @return The handle to resume immediately, or `noop_coroutine()` when
158   the wait was published to the timer service. 158   the wait was published to the timer service.
159   */ 159   */
160   std::coroutine_handle<> 160   std::coroutine_handle<>
HITCBC 161   21010 await_suspend(std::coroutine_handle<> h, capy::await_context const* env) 161   20812 await_suspend(std::coroutine_handle<> h, capy::await_context const* env)
162   { 162   {
HITCBC 163   21010 if (env->stop_token.stop_requested()) 163   20812 if (env->stop_token.stop_requested())
164   { 164   {
HITCBC 165   3416 canceled_ = true; 165   3188 canceled_ = true;
HITCBC 166   3416 return h; 166   3188 return h;
167   } 167   }
168   168  
169   // Elapsed deadlines complete synchronously, but only once a 169   // Elapsed deadlines complete synchronously, but only once a
170   // pending stop request has already been ruled out above. 170   // pending stop request has already been ruled out above.
HITCBC 171   35174 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now() 171   35234 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now()
HITCBC 172   17580 : dur_.count() <= 0) 172   17610 : dur_.count() <= 0)
HITCBC 173   208 return h; 173   286 return h;
174   174  
HITCBC 175   17386 detail::emplace_delay_timer(timer_, env->executor.context()); 175   17338 detail::emplace_delay_timer(timer_, env->executor.context());
176   176  
HITCBC 177   17384 if (has_deadline_) 177   17336 if (has_deadline_)
HITCBC 178   12 timer_->expires_at(deadline_); 178   12 timer_->expires_at(deadline_);
179   else 179   else
HITCBC 180   17372 timer_->expires_after(dur_); 180   17324 timer_->expires_after(dur_);
181   181  
HITCBC 182   17384 wait_.emplace(timer_->wait()); 182   17336 wait_.emplace(timer_->wait());
HITCBC 183   17384 return wait_->await_suspend(h, env); 183   17336 return wait_->await_suspend(h, env);
184   } 184   }
185   185  
186   /// Return empty on expiry, `error::canceled` if stop won. 186   /// Return empty on expiry, `error::canceled` if stop won.
HITCBC 187   17767 [[nodiscard]] capy::results<> await_resume() noexcept 187   17569 [[nodiscard]] capy::results<> await_resume() noexcept
188   { 188   {
HITCBC 189   17767 if (canceled_) 189   17569 if (canceled_)
HITCBC 190   3416 return {capy::error::canceled}; 190   3188 return {capy::error::canceled};
HITCBC 191   14351 if (wait_) 191   14381 if (wait_)
HITCBC 192   14143 return wait_->await_resume(); 192   14095 return wait_->await_resume();
HITCBC 193   208 return {}; 193   286 return {};
194   } 194   }
195   }; 195   };
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 is a 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 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 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. 201   waiter is re-published for the next `Traits::to_wait_duration` cap.
202   That re-publish neither resumes the coroutine nor allocates. 202   That re-publish neither resumes the coroutine nor allocates.
203   203  
204   Not intended to be named directly. Use the @ref delay factory 204   Not intended to be named directly. Use the @ref delay factory
205   overloads instead. 205   overloads instead.
206   206  
207   @tparam Clock The clock the deadline is expressed in. 207   @tparam Clock The clock the deadline is expressed in.
208   @tparam Traits The wait-traits policy bounding each steady-clock wait. 208   @tparam Traits The wait-traits policy bounding each steady-clock wait.
209   209  
210   @pre The awaiting coroutine's executor must belong to an 210   @pre The awaiting coroutine's executor must belong to an
211   `io_context`. Any other execution context terminates with a 211   `io_context`. Any other execution context terminates with a
212   diagnostic, because silently running without a timer would 212   diagnostic, because silently running without a timer would
213   drop the requested delay. 213   drop the requested delay.
214   214  
215   @par Cancellation 215   @par Cancellation
216   Identical to @ref delay_awaitable: stop already requested 216   Identical to @ref delay_awaitable: stop already requested
217   resumes inline with `error::canceled`; stop while suspended 217   resumes inline with `error::canceled`; stop while suspended
218   cancels the pending wait, including between re-arms. 218   cancels the pending wait, including between re-arms.
219   219  
220 - @see @ref delay, @ref wait_traits 220 + @see delay, wait_traits
221   */ 221   */
222   template<class Clock, class Traits> 222   template<class Clock, class Traits>
223   class clock_delay_awaitable 223   class clock_delay_awaitable
224   { 224   {
225   typename Clock::time_point deadline_{}; 225   typename Clock::time_point deadline_{};
226   bool canceled_ = false; 226   bool canceled_ = false;
227   std::optional<detail::timer> timer_; 227   std::optional<detail::timer> timer_;
228   detail::waiter_node w_; 228   detail::waiter_node w_;
229   229  
230   std::chrono::nanoseconds 230   std::chrono::nanoseconds
HITCBC 231   6913 next_wait(typename Clock::time_point now) const noexcept 231   5547 next_wait(typename Clock::time_point now) const noexcept
232   { 232   {
HITCBC 233   6913 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now)); 233   5547 return detail::clamp_to_ns(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 dispatched, so the frame cannot die
238   // concurrently. 238   // concurrently.
HITCBC 239   6911 static bool on_fire(void* ctx) noexcept 239   5545 static bool on_fire(void* ctx) noexcept
240   { 240   {
HITCBC 241   6911 auto* self = static_cast<clock_delay_awaitable*>(ctx); 241   5545 auto* self = static_cast<clock_delay_awaitable*>(ctx);
242   // Canceled: resume and surface the error 242   // Canceled: resume and surface the error
HITCBC 243   6911 if (self->w_.ec_) 243   5545 if (self->w_.ec_)
HITCBC 244   602 return false; 244   314 return false;
HITCBC 245   6309 auto now = Clock::now(); 245   5231 auto now = Clock::now();
HITCBC 246   6309 if (now >= self->deadline_) 246   5231 if (now >= self->deadline_)
HITCBC 247   264 return false; 247   258 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   6045 if (self->timer_->rearm_wait(self->w_, self->next_wait(now))) 250   4973 if (self->timer_->rearm_wait(self->w_, self->next_wait(now)))
HITCBC 251   6045 return true; 251   4973 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   1274 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept 260   1268 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept
HITCBC 261   1274 : deadline_(tp) 261   1268 : deadline_(tp)
262   { 262   {
HITCBC 263   1274 } 263   1268 }
264   264  
265   // 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`. 266   /// Construct by transferring the deadline from `other`.
HITCBC 267   1274 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept 267   1268 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept
HITCBC 268   1274 : deadline_(other.deadline_) 268   1268 : deadline_(other.deadline_)
269   { 269   {
HITCBC 270   1274 } 270   1268 }
271   271  
272   /// Copy construction is disabled; an awaitable owns its timer. 272   /// Copy construction is disabled; an awaitable owns its timer.
273   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. 274   /// Copy assignment is disabled; an awaitable owns its timer.
275   clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete; 275   clock_delay_awaitable& operator=(clock_delay_awaitable const&) = delete;
276   /// Move assignment is disabled; an awaitable is moved only before it is awaited. 276   /// Move assignment is disabled; an awaitable is moved only before it is awaited.
277   clock_delay_awaitable& operator=(clock_delay_awaitable&&) = delete; 277   clock_delay_awaitable& 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   1274 bool await_ready() const noexcept 282   1268 bool await_ready() const noexcept
283   { 283   {
HITCBC 284   1274 return false; 284   1268 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   288  
289   @param h Coroutine handle to resume on completion. 289   @param h Coroutine handle to resume on completion.
290   @param env The I/O environment, carrying the executor, stop token 290   @param env The I/O environment, carrying the executor, stop token
291   and frame allocator. 291   and frame allocator.
292   292  
293   @return The handle to resume immediately, or `noop_coroutine()` when 293   @return The handle to resume immediately, or `noop_coroutine()` when
294   the wait was published to the timer service. 294   the wait was published to the timer service.
295   */ 295   */
296   std::coroutine_handle<> 296   std::coroutine_handle<>
HITCBC 297   1274 await_suspend(std::coroutine_handle<> h, capy::await_context const* env) 297   1268 await_suspend(std::coroutine_handle<> h, capy::await_context const* env)
298   { 298   {
HITCBC 299   1274 if (env->stop_token.stop_requested()) 299   1268 if (env->stop_token.stop_requested())
300   { 300   {
HITCBC 301   404 canceled_ = true; 301   692 canceled_ = true;
HITCBC 302   404 return h; 302   692 return h;
303   } 303   }
304   304  
HITCBC 305   870 auto now = Clock::now(); 305   576 auto now = Clock::now();
HITCBC 306   870 if (now >= deadline_) 306   576 if (now >= deadline_)
HITCBC 307   2 return h; 307   2 return h;
308   308  
HITCBC 309   868 detail::emplace_delay_timer(timer_, env->executor.context()); 309   574 detail::emplace_delay_timer(timer_, env->executor.context());
310   310  
HITCBC 311   868 timer_->expires_after(next_wait(now)); 311   574 timer_->expires_after(next_wait(now));
312   312  
HITCBC 313   868 w_.bind(h, *env); 313   574 w_.bind(h, *env);
HITCBC 314   868 w_.on_fire_ = &on_fire; 314   574 w_.on_fire_ = &on_fire;
HITCBC 315   868 w_.on_fire_ctx_ = this; 315   574 w_.on_fire_ctx_ = this;
316   // Never the elapsed fast path: a capped expiry that elapses 316   // Never the elapsed fast path: a capped expiry that elapses
317   // before publication must still reach on_fire, not complete 317   // before publication must still reach on_fire, not complete
318   // the clock wait early. 318   // the clock wait early.
HITCBC 319   868 return timer_->publish_wait(w_); 319   574 return timer_->publish_wait(w_);
320   } 320   }
321   321  
322   /// Return empty on deadline, `error::canceled` if stop won. 322   /// Return empty on deadline, `error::canceled` if stop won.
HITCBC 323   1272 [[nodiscard]] capy::results<> await_resume() noexcept 323   1266 [[nodiscard]] capy::results<> await_resume() noexcept
324   { 324   {
HITCBC 325   1272 if (canceled_) 325   1266 if (canceled_)
HITCBC 326   404 return {capy::error::canceled}; 326   692 return {capy::error::canceled};
HITCBC 327   868 if (timer_) 327   574 if (timer_)
HITCBC 328   866 return {w_.ec_}; 328   572 return {w_.ec_};
HITCBC 329   2 return {}; 329   2 return {};
330   } 330   }
331   }; 331   };
332   332  
333   /** Suspend the current coroutine for a duration. 333   /** Suspend the current coroutine for a duration.
334   334  
335   Returns a ContextAwaitable that completes at or after the 335   Returns a ContextAwaitable that completes at or after the
336   specified duration, or earlier if the environment's stop 336   specified duration, or earlier if the environment's stop
337   token is activated. Zero or negative durations complete 337   token is activated. Zero or negative durations complete
338   synchronously. 338   synchronously.
339   339  
340   @par Example 340   @par Example
341   @par !example duration 341   @par !example duration
342   342  
343   @param dur The duration to wait. 343   @param dur The duration to wait.
344   344  
345   @return A @ref delay_awaitable yielding `results<>`. 345   @return A @ref delay_awaitable yielding `results<>`.
346   */ 346   */
347   template<typename Rep, typename Period> 347   template<typename Rep, typename Period>
348   [[nodiscard]] delay_awaitable 348   [[nodiscard]] delay_awaitable
HITCBC 349   20992 delay(std::chrono::duration<Rep, Period> dur) noexcept 349   20794 delay(std::chrono::duration<Rep, Period> dur) noexcept
350   { 350   {
HITCBC 351   20992 return delay_awaitable(detail::clamp_to_ns(dur)); 351   20794 return delay_awaitable(detail::clamp_to_ns(dur));
352   } 352   }
353   353  
354   /** Suspend the current coroutine until a time point. 354   /** Suspend the current coroutine until a time point.
355   355  
356   Returns a ContextAwaitable that completes at or after `tp`, or 356   Returns a ContextAwaitable that completes at or after `tp`, or
357   earlier if the environment's stop token is activated. Time 357   earlier if the environment's stop token is activated. Time
358   points already reached complete synchronously. 358   points already reached complete synchronously.
359   359  
360   @param tp The steady-clock time point to wait until. 360   @param tp The steady-clock time point to wait until.
361   361  
362   @return A @ref delay_awaitable yielding `results<>`. 362   @return A @ref delay_awaitable yielding `results<>`.
363   */ 363   */
364   [[nodiscard]] inline delay_awaitable 364   [[nodiscard]] inline delay_awaitable
HITCBC 365   16 delay(std::chrono::steady_clock::time_point tp) noexcept 365   16 delay(std::chrono::steady_clock::time_point tp) noexcept
366   { 366   {
HITCBC 367   16 return delay_awaitable(tp); 367   16 return delay_awaitable(tp);
368   } 368   }
369   369  
370   /** Suspend the current coroutine until a time point on `Clock`. 370   /** Suspend the current coroutine until a time point on `Clock`.
371   371  
372   Returns a ContextAwaitable that completes at or after the first 372   Returns a ContextAwaitable that completes at or after the first
373   observation of `Clock::now() >= tp`, or earlier if the 373   observation of `Clock::now() >= tp`, or earlier if the
374   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
375   bounded steady-clock waits, re-reading `Clock::now()` after 375   bounded steady-clock waits, re-reading `Clock::now()` after
376   each. `Traits::to_wait_duration` bounds each one. With the default 376   each. `Traits::to_wait_duration` bounds each one. With the default
377   @ref wait_traits a single full-length wait is used. An adjustment of 377   @ref wait_traits a single full-length wait is used. An adjustment of
378   `Clock` mid-wait is therefore observed only at natural wakeup. 378   `Clock` mid-wait is therefore observed only at natural wakeup.
379   Supply capping traits to bound that latency. Time 379   Supply capping traits to bound that latency. Time
380   points already reached complete synchronously. 380   points already reached complete synchronously.
381   381  
382   @note `Clock::now()` and `Traits::to_wait_duration` are invoked 382   @note `Clock::now()` and `Traits::to_wait_duration` are invoked
383   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.
384   384  
385   @par Example 385   @par Example
386   @par !example system_clock_deadline 386   @par !example system_clock_deadline
387   387  
388   @tparam Traits The wait-traits policy; `void` selects 388   @tparam Traits The wait-traits policy; `void` selects
389   @ref wait_traits. 389   @ref wait_traits.
390   390  
391   @tparam Clock The clock type. This overload does not participate 391   @tparam Clock The clock type. This overload does not participate
392   when `Clock` is `std::chrono::steady_clock`. The dedicated 392   when `Clock` is `std::chrono::steady_clock`. The dedicated
393   @ref delay overload taking a `steady_clock::time_point` handles 393   @ref delay overload taking a `steady_clock::time_point` handles
394   that case. 394   that case.
395   395  
396   @param tp The time point to wait until. 396   @param tp The time point to wait until.
397   397  
398   @return A @ref clock_delay_awaitable yielding `results<>`. 398   @return A @ref clock_delay_awaitable yielding `results<>`.
399   */ 399   */
400   template<class Traits = void, class Clock, class Duration> 400   template<class Traits = void, class Clock, class Duration>
401   requires(!std::same_as<Clock, std::chrono::steady_clock>) && 401   requires(!std::same_as<Clock, std::chrono::steady_clock>) &&
402   (std::is_void_v<Traits> || WaitTraits<Traits, Clock>) 402   (std::is_void_v<Traits> || WaitTraits<Traits, Clock>)
403   [[nodiscard]] auto 403   [[nodiscard]] auto
HITCBC 404   1274 delay(std::chrono::time_point<Clock, Duration> tp) noexcept 404   1268 delay(std::chrono::time_point<Clock, Duration> tp) noexcept
405   { 405   {
406   using traits_type = 406   using traits_type =
407   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>;
408   // ceil preserves completes-at-or-after when Duration is coarser 408   // ceil preserves completes-at-or-after when Duration is coarser
409   // than the clock's native duration 409   // than the clock's native duration
410   return clock_delay_awaitable<Clock, traits_type>( 410   return clock_delay_awaitable<Clock, traits_type>(
HITCBC 411   1274 std::chrono::ceil<typename Clock::duration>(tp)); 411   1268 std::chrono::ceil<typename Clock::duration>(tp));
412   } 412   }
413   413  
414   } // namespace boost::corosio 414   } // namespace boost::corosio
415   415  
416   #endif 416   #endif