include/boost/corosio/delay.hpp

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