include/boost/corosio/delay.hpp

93.3% Lines (42/45) 100.0% List of functions (11/11)
delay.hpp
f(x) Functions (11)
Function Calls Lines Blocks
boost::corosio::delay_awaitable::delay_awaitable(std::chrono::duration<long, std::ratio<1l, 1000000000l> >) :75 17190x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >) :81 9x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(boost::corosio::delay_awaitable&&) :90 20241x 100.0% 100.0% boost::corosio::delay_awaitable::await_ready() const :99 14154x 100.0% 100.0% boost::corosio::delay_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :106 17199x 85.0% 82.0% boost::corosio::delay_awaitable::await_resume() :148 17160x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<int, std::ratio<1l, 1000l> >(std::chrono::duration<int, std::ratio<1l, 1000l> >) :176 3x 100.0% 89.0% boost::corosio::delay_awaitable boost::corosio::delay<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> >) :176 3339x 100.0% 89.0% boost::corosio::delay_awaitable boost::corosio::delay<long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> >) :176 6x 100.0% 89.0% boost::corosio::delay_awaitable boost::corosio::delay<long, std::ratio<3600l, 1l> >(std::chrono::duration<long, std::ratio<3600l, 1l> >) :176 39x 100.0% 100.0% boost::corosio::delay(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >) :201 9x 100.0% 100.0%
Line 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 17190x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
76 17190x : dur_(dur)
77 {
78 17190x }
79
80 /// Construct an awaitable that waits until `tp`.
81 9x explicit delay_awaitable(
82 std::chrono::steady_clock::time_point tp) noexcept
83 9x : deadline_(tp)
84 9x , has_deadline_(true)
85 {
86 9x }
87
88 /// Construct by transferring state from `other`.
89 // Only moved before await_suspend; wait_ is engaged after.
90 20241x 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 14154x bool await_ready() const noexcept
100 {
101 14154x return false;
102 }
103
104 /// Resume inline if stopped or elapsed; else wait on a timer.
105 std::coroutine_handle<>
106 17199x await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
107 {
108 17199x if(env->stop_token.stop_requested())
109 {
110 6011x canceled_ = true;
111 6011x return h;
112 }
113
114 // Elapsed deadlines complete synchronously, but only once a
115 // pending stop request has already been ruled out above.
116 22370x if(has_deadline_ ?
117 11188x deadline_ <= std::chrono::steady_clock::now() :
118 11182x dur_.count() <= 0)
119 10x 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 11178x timer_.emplace(env->executor.context());
127 }
128 3x catch(std::logic_error const&)
129 {
130 3x detail::throw_logic_error(
131 "delay requires an io_context-backed executor");
132 3x }
133 catch(std::exception const& e)
134 {
135 detail::throw_logic_error(e.what());
136 }
137
138 11175x if(has_deadline_)
139 3x timer_->expires_at(deadline_);
140 else
141 11172x timer_->expires_after(dur_);
142
143 11175x wait_.emplace(timer_->wait());
144 11175x return wait_->await_suspend(h, env);
145 }
146
147 /// Return empty on expiry, `error::canceled` if stop won.
148 17160x capy::io_result<> await_resume() noexcept
149 {
150 17160x if(canceled_)
151 6011x return {capy::error::canceled};
152 11149x if(wait_)
153 11139x return wait_->await_resume();
154 10x 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 17187x 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 31293x auto ns = dsec(dur) >= dsec((nanoseconds::max)())
183 31293x ? (nanoseconds::max)()
184 17184x : dsec(dur) <= dsec((nanoseconds::min)())
185 17184x ? (nanoseconds::min)()
186 17181x : duration_cast<nanoseconds>(dur);
187 17187x 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 9x delay(std::chrono::steady_clock::time_point tp) noexcept
202 {
203 9x return delay_awaitable(tp);
204 }
205
206 } // namespace boost::corosio
207
208 #endif
209