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   20617 clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept 39   20838 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   20615 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)() 50   20836 return dsec(dur) >= dsec((nanoseconds::max)()) ? (nanoseconds::max)()
HITCBC 51   41228 : dsec(dur) <= dsec((nanoseconds::min)()) 51   41670 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 52   20613 ? (nanoseconds::min)() 52   20834 ? (nanoseconds::min)()
HITCBC 53   20615 : duration_cast<nanoseconds>(dur); 53   20836 : 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   12775 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx) 60   13035 emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx)
61   { 61   {
62   try 62   try
63   { 63   {
HITCBC 64   12775 t.emplace(ctx); 64   13035 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   12773 } 74   13033 }
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 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   16406 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur) 121   16456 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept : dur_(dur)
122   { 122   {
HITCBC 123   16406 } 123   16456 }
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   18450 delay_awaitable(delay_awaitable&&) = default; 134   18500 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   16420 bool await_ready() const noexcept 146   16470 bool await_ready() const noexcept
147   { 147   {
HITCBC 148   16420 return false; 148   16470 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   16422 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 161   16472 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
162   { 162   {
HITCBC 163   16422 if (env->stop_token.stop_requested()) 163   16472 if (env->stop_token.stop_requested())
164   { 164   {
HITCBC 165   3813 canceled_ = true; 165   3495 canceled_ = true;
HITCBC 166   3813 return h; 166   3495 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   25204 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now() 171   25940 if (has_deadline_ ? deadline_ <= std::chrono::steady_clock::now()
HITCBC 172   12595 : dur_.count() <= 0) 172   12963 : dur_.count() <= 0)
HITCBC 173   75 return h; 173   183 return h;
174   174  
HITCBC 175   12534 detail::emplace_delay_timer(timer_, env->executor.context()); 175   12794 detail::emplace_delay_timer(timer_, env->executor.context());
176   176  
HITCBC 177   12532 if (has_deadline_) 177   12792 if (has_deadline_)
HITCBC 178   12 timer_->expires_at(deadline_); 178   12 timer_->expires_at(deadline_);
179   else 179   else
HITCBC 180   12520 timer_->expires_after(dur_); 180   12780 timer_->expires_after(dur_);
181   181  
HITCBC 182   12532 wait_.emplace(timer_->wait()); 182   12792 wait_.emplace(timer_->wait());
HITCBC 183   12532 return wait_->await_suspend(h, env); 183   12792 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   16395 [[nodiscard]] capy::io_result<> await_resume() noexcept 187   16445 [[nodiscard]] capy::io_result<> await_resume() noexcept
188   { 188   {
HITCBC 189   16395 if (canceled_) 189   16445 if (canceled_)
HITCBC 190   3813 return {capy::error::canceled}; 190   3495 return {capy::error::canceled};
HITCBC 191   12582 if (wait_) 191   12950 if (wait_)
HITCBC 192   12507 return wait_->await_resume(); 192   12767 return wait_->await_resume();
HITCBC 193   75 return {}; 193   183 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 delay, 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   4213 next_wait(typename Clock::time_point now) const noexcept 231   4384 next_wait(typename Clock::time_point now) const noexcept
232   { 232   {
HITCBC 233   4213 return detail::clamp_to_ns(Traits::to_wait_duration(deadline_ - now)); 233   4384 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 posted, so the frame cannot die
238   // concurrently. 238   // concurrently.
HITCBC 239   4211 static bool on_fire(void* ctx) noexcept 239   4382 static bool on_fire(void* ctx) noexcept
240   { 240   {
HITCBC 241   4211 auto* self = static_cast<clock_delay_awaitable*>(ctx); 241   4382 auto* self = static_cast<clock_delay_awaitable*>(ctx);
242   // Canceled: resume and surface the error 242   // Canceled: resume and surface the error
HITCBC 243   4211 if (self->w_.ec_) 243   4382 if (self->w_.ec_)
HITCBC 244   2 return false; 244   2 return false;
HITCBC 245   4209 auto now = Clock::now(); 245   4380 auto now = Clock::now();
HITCBC 246   4209 if (now >= self->deadline_) 246   4380 if (now >= self->deadline_)
HITCBC 247   237 return false; 247   237 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   3972 if (self->timer_->rearm_wait(self->w_, self->next_wait(now))) 250   4143 if (self->timer_->rearm_wait(self->w_, self->next_wait(now)))
HITCBC 251   3972 return true; 251   4143 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   1247 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept 260   1247 explicit clock_delay_awaitable(typename Clock::time_point tp) noexcept
HITCBC 261   1247 : deadline_(tp) 261   1247 : deadline_(tp)
262   { 262   {
HITCBC 263   1247 } 263   1247 }
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   1247 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept 267   1247 clock_delay_awaitable(clock_delay_awaitable&& other) noexcept
HITCBC 268   1247 : deadline_(other.deadline_) 268   1247 : deadline_(other.deadline_)
269   { 269   {
HITCBC 270   1247 } 270   1247 }
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   1247 bool await_ready() const noexcept 282   1247 bool await_ready() const noexcept
283   { 283   {
HITCBC 284   1247 return false; 284   1247 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   1247 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 297   1247 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
298   { 298   {
HITCBC 299   1247 if (env->stop_token.stop_requested()) 299   1247 if (env->stop_token.stop_requested())
300   { 300   {
HITCBC 301   1004 canceled_ = true; 301   1004 canceled_ = true;
HITCBC 302   1004 return h; 302   1004 return h;
303   } 303   }
304   304  
HITCBC 305   243 auto now = Clock::now(); 305   243 auto now = Clock::now();
HITCBC 306   243 if (now >= deadline_) 306   243 if (now >= deadline_)
HITCBC 307   2 return h; 307   2 return h;
308   308  
HITCBC 309   241 detail::emplace_delay_timer(timer_, env->executor.context()); 309   241 detail::emplace_delay_timer(timer_, env->executor.context());
310   310  
HITCBC 311   241 timer_->expires_after(next_wait(now)); 311   241 timer_->expires_after(next_wait(now));
312   312  
HITCBC 313   241 w_.bind(h, *env); 313   241 w_.bind(h, *env);
HITCBC 314   241 w_.on_fire_ = &on_fire; 314   241 w_.on_fire_ = &on_fire;
HITCBC 315   241 w_.on_fire_ctx_ = this; 315   241 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   241 return timer_->publish_wait(w_); 319   241 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   1245 [[nodiscard]] capy::io_result<> await_resume() noexcept 323   1245 [[nodiscard]] capy::io_result<> await_resume() noexcept
324   { 324   {
HITCBC 325   1245 if (canceled_) 325   1245 if (canceled_)
HITCBC 326   1004 return {capy::error::canceled}; 326   1004 return {capy::error::canceled};
HITCBC 327   241 if (timer_) 327   241 if (timer_)
HITCBC 328   239 return {w_.ec_}; 328   239 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 an IoAwaitable that completes at or after the 335   Returns an IoAwaitable 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 `io_result<>`. 345   @return A @ref delay_awaitable yielding `io_result<>`.
346   */ 346   */
347   template<typename Rep, typename Period> 347   template<typename Rep, typename Period>
348   [[nodiscard]] delay_awaitable 348   [[nodiscard]] delay_awaitable
HITCBC 349   16404 delay(std::chrono::duration<Rep, Period> dur) noexcept 349   16454 delay(std::chrono::duration<Rep, Period> dur) noexcept
350   { 350   {
HITCBC 351   16404 return delay_awaitable(detail::clamp_to_ns(dur)); 351   16454 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 an IoAwaitable that completes at or after `tp`, or 356   Returns an IoAwaitable 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 `io_result<>`. 362   @return A @ref delay_awaitable yielding `io_result<>`.
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 an IoAwaitable that completes at or after the first 372   Returns an IoAwaitable 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 `io_result<>`. 398   @return A @ref clock_delay_awaitable yielding `io_result<>`.
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   1247 delay(std::chrono::time_point<Clock, Duration> tp) noexcept 404   1247 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   1247 std::chrono::ceil<typename Clock::duration>(tp)); 411   1247 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