93.33% Lines (42/45) 100.00% Functions (8/8)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_DELAY_HPP 10   #ifndef BOOST_COROSIO_DELAY_HPP
11   #define BOOST_COROSIO_DELAY_HPP 11   #define BOOST_COROSIO_DELAY_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/except.hpp> 14   #include <boost/corosio/detail/except.hpp>
15   #include <boost/corosio/detail/timer.hpp> 15   #include <boost/corosio/detail/timer.hpp>
16   #include <boost/capy/error.hpp> 16   #include <boost/capy/error.hpp>
17   #include <boost/capy/ex/io_env.hpp> 17   #include <boost/capy/ex/io_env.hpp>
18   #include <boost/capy/io_result.hpp> 18   #include <boost/capy/io_result.hpp>
19   19  
20   #include <chrono> 20   #include <chrono>
21   #include <coroutine> 21   #include <coroutine>
22   #include <exception> 22   #include <exception>
23   #include <optional> 23   #include <optional>
24   #include <stdexcept> 24   #include <stdexcept>
25   25  
26   namespace boost::corosio { 26   namespace boost::corosio {
27   27  
28   /** IoAwaitable returned by @ref delay. 28   /** IoAwaitable returned by @ref delay.
29   29  
30   Suspends the calling coroutine until the deadline elapses or 30   Suspends the calling coroutine until the deadline elapses or
31   the environment's stop token is activated, whichever comes 31   the environment's stop token is activated, whichever comes
32   first. A deadline already elapsed at suspension, or a stop 32   first. A deadline already elapsed at suspension, or a stop
33   token already active, resumes the coroutine inline, without 33   token already active, resumes the coroutine inline, without
34   starting a timer (see Cancellation below). Otherwise the 34   starting a timer (see Cancellation below). Otherwise the
35   coroutine resumes through the executor once the timer fires 35   coroutine resumes through the executor once the timer fires
36   or a mid-wait cancellation arrives. 36   or a mid-wait cancellation arrives.
37   37  
38   Not intended to be named directly; use the @ref delay factory 38   Not intended to be named directly; use the @ref delay factory
39   overloads instead. 39   overloads instead.
40   40  
41   @par Preconditions 41   @par Preconditions
42   The awaiting coroutine's executor must belong to an 42   The awaiting coroutine's executor must belong to an
43   `io_context`. Any other execution context terminates with a 43   `io_context`. Any other execution context terminates with a
44   diagnostic, because silently running without a timer would 44   diagnostic, because silently running without a timer would
45   drop the requested delay. 45   drop the requested delay.
46   46  
47   @par Cancellation 47   @par Cancellation
48   If stop is already requested before suspension, the coroutine 48   If stop is already requested before suspension, the coroutine
49   resumes immediately with `error::canceled`. If stop is 49   resumes immediately with `error::canceled`. If stop is
50   requested while suspended, the pending wait is cancelled and 50   requested while suspended, the pending wait is cancelled and
51   the coroutine resumes with `error::canceled`. Requesting stop 51   the coroutine resumes with `error::canceled`. Requesting stop
52   from another thread while the io_context runs in 52   from another thread while the io_context runs in
53   single_threaded mode (auto-enabled at concurrency_hint == 1) 53   single_threaded mode (auto-enabled at concurrency_hint == 1)
54   is not permitted by io_context's threading rules; 54   is not permitted by io_context's threading rules;
55   cross-thread cancellation requires a multi-threaded-capable 55   cross-thread cancellation requires a multi-threaded-capable
56   context. 56   context.
57   57  
58   @see delay 58   @see delay
59   */ 59   */
60   class delay_awaitable 60   class delay_awaitable
61   { 61   {
62   // wait() names timer's private awaitable type; decltype is 62   // wait() names timer's private awaitable type; decltype is
63   // the only way to store it here. 63   // the only way to store it here.
64   using wait_type = decltype(std::declval<detail::timer&>().wait()); 64   using wait_type = decltype(std::declval<detail::timer&>().wait());
65   65  
66   std::chrono::steady_clock::time_point deadline_{}; 66   std::chrono::steady_clock::time_point deadline_{};
67   std::chrono::nanoseconds dur_{}; 67   std::chrono::nanoseconds dur_{};
68   bool has_deadline_ = false; 68   bool has_deadline_ = false;
69   bool canceled_ = false; 69   bool canceled_ = false;
70   std::optional<detail::timer> timer_; 70   std::optional<detail::timer> timer_;
71   std::optional<wait_type> wait_; 71   std::optional<wait_type> wait_;
72   72  
73   public: 73   public:
74   /// Construct an awaitable that waits for `dur` nanoseconds. 74   /// Construct an awaitable that waits for `dur` nanoseconds.
HITCBC 75   11382 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept 75   10424 explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
HITCBC 76   11382 : dur_(dur) 76   10424 : dur_(dur)
77   { 77   {
HITCBC 78   11382 } 78   10424 }
79   79  
80   /// Construct an awaitable that waits until `tp`. 80   /// Construct an awaitable that waits until `tp`.
HITCBC 81   6 explicit delay_awaitable( 81   6 explicit delay_awaitable(
82   std::chrono::steady_clock::time_point tp) noexcept 82   std::chrono::steady_clock::time_point tp) noexcept
HITCBC 83   6 : deadline_(tp) 83   6 : deadline_(tp)
HITCBC 84   6 , has_deadline_(true) 84   6 , has_deadline_(true)
85   { 85   {
HITCBC 86   6 } 86   6 }
87   87  
88   /// Construct by transferring state from `other`. 88   /// Construct by transferring state from `other`.
89   // Only moved before await_suspend; wait_ is engaged after. 89   // Only moved before await_suspend; wait_ is engaged after.
HITCBC 90   13416 delay_awaitable(delay_awaitable&&) = default; 90   12458 delay_awaitable(delay_awaitable&&) = default;
91   91  
92   delay_awaitable(delay_awaitable const&) = delete; 92   delay_awaitable(delay_awaitable const&) = delete;
93   delay_awaitable& operator=(delay_awaitable const&) = delete; 93   delay_awaitable& operator=(delay_awaitable const&) = delete;
94   delay_awaitable& operator=(delay_awaitable&&) = delete; 94   delay_awaitable& operator=(delay_awaitable&&) = delete;
95   95  
96   /// Return false unconditionally; see await_suspend. 96   /// Return false unconditionally; see await_suspend.
97   // The elapsed-deadline fast path must run after the stop-token 97   // The elapsed-deadline fast path must run after the stop-token
98   // check, and only await_suspend receives the env carrying it. 98   // check, and only await_suspend receives the env carrying it.
HITCBC 99   9358 bool await_ready() const noexcept 99   8400 bool await_ready() const noexcept
100   { 100   {
HITCBC 101   9358 return false; 101   8400 return false;
102   } 102   }
103   103  
104   /// Resume inline if stopped or elapsed; else wait on a timer. 104   /// Resume inline if stopped or elapsed; else wait on a timer.
105   std::coroutine_handle<> 105   std::coroutine_handle<>
HITCBC 106   11388 await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 106   10430 await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
107   { 107   {
HITCBC 108   11388 if(env->stop_token.stop_requested()) 108   10430 if(env->stop_token.stop_requested())
109   { 109   {
HITCBC 110   3947 canceled_ = true; 110   4003 canceled_ = true;
HITCBC 111   3947 return h; 111   4003 return h;
112   } 112   }
113   113  
114   // Elapsed deadlines complete synchronously, but only once a 114   // Elapsed deadlines complete synchronously, but only once a
115   // pending stop request has already been ruled out above. 115   // pending stop request has already been ruled out above.
HITCBC 116   14878 if(has_deadline_ ? 116   12850 if(has_deadline_ ?
HITCBC 117   7441 deadline_ <= std::chrono::steady_clock::now() : 117   6427 deadline_ <= std::chrono::steady_clock::now() :
HITCBC 118   7437 dur_.count() <= 0) 118   6423 dur_.count() <= 0)
HITCBC 119   27 return h; 119   8 return h;
120   120  
121   // A non-io_context executor cannot supply a timer service, 121   // A non-io_context executor cannot supply a timer service,
122   // and await_suspend is driven through a noexcept wrapper, so 122   // and await_suspend is driven through a noexcept wrapper, so
123   // translate the service-lookup failure into a clear terminate. 123   // translate the service-lookup failure into a clear terminate.
124   try 124   try
125   { 125   {
HITCBC 126   7414 timer_.emplace(env->executor.context()); 126   6419 timer_.emplace(env->executor.context());
127   } 127   }
HITCBC 128   2 catch(std::logic_error const&) 128   2 catch(std::logic_error const&)
129   { 129   {
HITCBC 130   2 detail::throw_logic_error( 130   2 detail::throw_logic_error(
131   "delay requires an io_context-backed executor"); 131   "delay requires an io_context-backed executor");
HITCBC 132   2 } 132   2 }
MISUBC 133   catch(std::exception const& e) 133   catch(std::exception const& e)
134   { 134   {
MISUBC 135   detail::throw_logic_error(e.what()); 135   detail::throw_logic_error(e.what());
MISUBC 136   } 136   }
137   137  
HITCBC 138   7412 if(has_deadline_) 138   6417 if(has_deadline_)
HITCBC 139   2 timer_->expires_at(deadline_); 139   2 timer_->expires_at(deadline_);
140   else 140   else
HITCBC 141   7410 timer_->expires_after(dur_); 141   6415 timer_->expires_after(dur_);
142   142  
HITCBC 143   7412 wait_.emplace(timer_->wait()); 143   6417 wait_.emplace(timer_->wait());
HITCBC 144   7412 return wait_->await_suspend(h, env); 144   6417 return wait_->await_suspend(h, env);
145   } 145   }
146   146  
147   /// Return empty on expiry, `error::canceled` if stop won. 147   /// Return empty on expiry, `error::canceled` if stop won.
HITCBC 148   11364 capy::io_result<> await_resume() noexcept 148   10406 capy::io_result<> await_resume() noexcept
149   { 149   {
HITCBC 150   11364 if(canceled_) 150   10406 if(canceled_)
HITCBC 151   3947 return {capy::error::canceled}; 151   4003 return {capy::error::canceled};
HITCBC 152   7417 if(wait_) 152   6403 if(wait_)
HITCBC 153   7390 return wait_->await_resume(); 153   6395 return wait_->await_resume();
HITCBC 154   27 return {}; 154   8 return {};
155   } 155   }
156   }; 156   };
157   157  
158   /** Suspend the current coroutine for a duration. 158   /** Suspend the current coroutine for a duration.
159   159  
160   Returns an IoAwaitable that completes at or after the 160   Returns an IoAwaitable that completes at or after the
161   specified duration, or earlier if the environment's stop 161   specified duration, or earlier if the environment's stop
162   token is activated. Zero or negative durations complete 162   token is activated. Zero or negative durations complete
163   synchronously. 163   synchronously.
164   164  
165   @par Example 165   @par Example
166   @code 166   @code
167   auto [ec] = co_await delay(std::chrono::milliseconds(100)); 167   auto [ec] = co_await delay(std::chrono::milliseconds(100));
168   @endcode 168   @endcode
169   169  
170   @param dur The duration to wait. 170   @param dur The duration to wait.
171   171  
172   @return A @ref delay_awaitable yielding `io_result<>`. 172   @return A @ref delay_awaitable yielding `io_result<>`.
173   */ 173   */
174   template<typename Rep, typename Period> 174   template<typename Rep, typename Period>
175   [[nodiscard]] delay_awaitable 175   [[nodiscard]] delay_awaitable
HITCBC 176   11380 delay(std::chrono::duration<Rep, Period> dur) noexcept 176   10422 delay(std::chrono::duration<Rep, Period> dur) noexcept
177   { 177   {
178   using namespace std::chrono; 178   using namespace std::chrono;
179   // Narrow reps wrap if nanoseconds::max() is converted into them; 179   // Narrow reps wrap if nanoseconds::max() is converted into them;
180   // a double comparison clamps safely in both directions. 180   // a double comparison clamps safely in both directions.
181   using dsec = duration<double>; 181   using dsec = duration<double>;
HITCBC 182   20708 auto ns = dsec(dur) >= dsec((nanoseconds::max)()) 182   18792 auto ns = dsec(dur) >= dsec((nanoseconds::max)())
HITCBC 183   20708 ? (nanoseconds::max)() 183   18792 ? (nanoseconds::max)()
HITCBC 184   11378 : dsec(dur) <= dsec((nanoseconds::min)()) 184   10420 : dsec(dur) <= dsec((nanoseconds::min)())
HITCBC 185   11378 ? (nanoseconds::min)() 185   10420 ? (nanoseconds::min)()
HITCBC 186   11376 : duration_cast<nanoseconds>(dur); 186   10418 : duration_cast<nanoseconds>(dur);
HITCBC 187   11380 return delay_awaitable(ns); 187   10422 return delay_awaitable(ns);
188   } 188   }
189   189  
190   /** Suspend the current coroutine until a time point. 190   /** Suspend the current coroutine until a time point.
191   191  
192   Returns an IoAwaitable that completes at or after `tp`, or 192   Returns an IoAwaitable that completes at or after `tp`, or
193   earlier if the environment's stop token is activated. Time 193   earlier if the environment's stop token is activated. Time
194   points already reached complete synchronously. 194   points already reached complete synchronously.
195   195  
196   @param tp The steady-clock time point to wait until. 196   @param tp The steady-clock time point to wait until.
197   197  
198   @return A @ref delay_awaitable yielding `io_result<>`. 198   @return A @ref delay_awaitable yielding `io_result<>`.
199   */ 199   */
200   [[nodiscard]] inline delay_awaitable 200   [[nodiscard]] inline delay_awaitable
HITCBC 201   6 delay(std::chrono::steady_clock::time_point tp) noexcept 201   6 delay(std::chrono::steady_clock::time_point tp) noexcept
202   { 202   {
HITCBC 203   6 return delay_awaitable(tp); 203   6 return delay_awaitable(tp);
204   } 204   }
205   205  
206   } // namespace boost::corosio 206   } // namespace boost::corosio
207   207  
208   #endif 208   #endif