include/boost/corosio/delay.hpp

98.1% Lines (51/52) 100.0% List of functions (14/14) 60.5% Branches (69/114)
delay.hpp
f(x) Functions (14)
Function Calls Lines Branches Blocks
boost::corosio::delay_awaitable::~delay_awaitable() :60 47843x 100.0% 50.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l>>) :75 21910x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(std::__1::chrono::time_point<std::__1::chrono::steady_clock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l>>>) :81 12x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(boost::corosio::delay_awaitable&&) :90 25935x 100.0% 100.0% boost::corosio::delay_awaitable::await_ready() const :99 8928x 100.0% 100.0% boost::corosio::delay_awaitable::await_suspend(std::__1::coroutine_handle<void>, boost::capy::io_env const*) :106 10957x 95.0% 75.0% 72.0% boost::corosio::delay_awaitable::await_resume() :148 10934x 100.0% 83.3% 87.0% boost::corosio::delay_awaitable boost::corosio::delay<int, std::__1::ratio<1l, 1000l>>(std::__1::chrono::duration<int, std::__1::ratio<1l, 1000l>>) :176 2x 100.0% 80.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::__1::ratio<1l, 1000000l>>(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000l>>) :176 5823x 100.0% 80.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::__1::ratio<1l, 1000l>>(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l>>) :176 5071x 71.4% 41.7% 80.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::__1::ratio<1l, 1l>>(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1l>>) :176 38x 71.4% 16.7% 80.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::__1::ratio<3600l, 1l>>(std::__1::chrono::duration<long long, std::__1::ratio<3600l, 1l>>) :176 2x 100.0% 40.0% 73.0% boost::corosio::delay_awaitable boost::corosio::delay<long, std::__1::ratio<3600l, 1l>>(std::__1::chrono::duration<long, std::__1::ratio<3600l, 1l>>) :176 26x 0.0% 0.0% 86.0% boost::corosio::delay(std::__1::chrono::time_point<std::__1::chrono::steady_clock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l>>>) :201 6x 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
1/2
✓ Branch 0 taken 10955 times.
✗ Branch 1 not taken.
10955x std::chrono::steady_clock::time_point deadline_{};
67 6x std::chrono::nanoseconds dur_{};
68 10955x bool has_deadline_ = false;
69 10961x 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 21910x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
76 10955x : dur_(dur)
77 10955x {
78 21910x }
79
80 /// Construct an awaitable that waits until `tp`.
81 18x explicit delay_awaitable(
82 std::chrono::steady_clock::time_point tp) noexcept
83 6x : deadline_(tp)
84 6x , has_deadline_(true)
85 6x {
86 12x }
87
88 /// Construct by transferring state from `other`.
89 // Only moved before await_suspend; wait_ is engaged after.
90 25935x 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 8928x bool await_ready() const noexcept
100 {
101 8928x return false;
102 }
103
104 /// Resume inline if stopped or elapsed; else wait on a timer.
105 std::coroutine_handle<>
106 10957x await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
107 {
108
2/2
✓ Branch 0 taken 4002 times.
✓ Branch 1 taken 6955 times.
10957x if(env->stop_token.stop_requested())
109 {
110 4002x canceled_ = true;
111 4002x return h;
112 }
113
114 // Elapsed deadlines complete synchronously, but only once a
115 // pending stop request has already been ruled out above.
116
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6951 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6949 times.
6955x if(has_deadline_ ?
117 4x deadline_ <= std::chrono::steady_clock::now() :
118 6951x dur_.count() <= 0)
119 6x 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
2/2
✓ Branch 0 taken 6947 times.
✓ Branch 1 taken 2 times.
6949x timer_.emplace(env->executor.context());
127 6951x }
128 catch(std::logic_error const&)
129 {
130
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x detail::throw_logic_error(
131 "delay requires an io_context-backed executor");
132 2x }
133 catch(std::exception const& e)
134 {
135 detail::throw_logic_error(e.what());
136 2x }
137
138
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6945 times.
6947x if(has_deadline_)
139 2x timer_->expires_at(deadline_);
140 else
141 6945x timer_->expires_after(dur_);
142
143
1/2
✓ Branch 0 taken 6947 times.
✗ Branch 1 not taken.
6947x wait_.emplace(timer_->wait());
144 6947x return wait_->await_suspend(h, env);
145 10959x }
146
147 /// Return empty on expiry, `error::canceled` if stop won.
148 10934x capy::io_result<> await_resume() noexcept
149 {
150
2/2
✓ Branch 0 taken 6929 times.
✓ Branch 1 taken 4005 times.
10934x if(canceled_)
151
1/2
✓ Branch 0 taken 4005 times.
✗ Branch 1 not taken.
4005x return {capy::error::canceled};
152
2/2
✓ Branch 0 taken 6923 times.
✓ Branch 1 taken 6 times.
6929x if(wait_)
153 6923x return wait_->await_resume();
154 6x return {};
155 10934x }
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 10962x 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
23/40
✓ Branch 0 taken 10882 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 10882 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10883 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 10883 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 38 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 38 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 38 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 38 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 26 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 26 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 26 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 24 times.
✓ Branch 23 taken 2 times.
✓ Branch 24 taken 2 times.
✗ Branch 25 not taken.
✓ Branch 26 taken 2 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 2 times.
✗ Branch 29 not taken.
✓ Branch 30 taken 2 times.
✗ Branch 31 not taken.
✓ Branch 32 taken 2 times.
✗ Branch 33 not taken.
✓ Branch 34 taken 2 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 2 times.
✗ Branch 37 not taken.
✓ Branch 38 taken 2 times.
✗ Branch 39 not taken.
21923x auto ns = dsec(dur) >= dsec((nanoseconds::max)())
183 2x ? (nanoseconds::max)()
184
23/40
✓ Branch 0 taken 10887 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 10885 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 10882 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 10882 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 38 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 38 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 38 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 38 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 24 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 24 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 24 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 24 times.
✗ Branch 23 not taken.
✓ Branch 24 taken 2 times.
✗ Branch 25 not taken.
✓ Branch 26 taken 2 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 2 times.
✗ Branch 29 not taken.
✓ Branch 30 taken 2 times.
✗ Branch 31 not taken.
✓ Branch 32 taken 2 times.
✗ Branch 33 not taken.
✓ Branch 34 taken 2 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 2 times.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✓ Branch 39 taken 2 times.
21897x : dsec(dur) <= dsec((nanoseconds::min)())
185 2x ? (nanoseconds::min)()
186
5/10
✓ Branch 0 taken 10884 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
10946x : duration_cast<nanoseconds>(dur);
187 10952x 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 6x delay(std::chrono::steady_clock::time_point tp) noexcept
202 {
203 6x return delay_awaitable(tp);
204 }
205
206 } // namespace boost::corosio
207
208 #endif
209