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 10424x 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 6x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(boost::corosio::delay_awaitable&&) :90 12458x 100.0% 100.0% boost::corosio::delay_awaitable::await_ready() const :99 8400x 100.0% 100.0% boost::corosio::delay_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :106 10430x 85.0% 84.0% boost::corosio::delay_awaitable::await_resume() :148 10406x 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 2x 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 2238x 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 4x 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 24x 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 6x 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 10424x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
76 10424x : dur_(dur)
77 {
78 10424x }
79
80 /// Construct an awaitable that waits until `tp`.
81 6x explicit delay_awaitable(
82 std::chrono::steady_clock::time_point tp) noexcept
83 6x : deadline_(tp)
84 6x , has_deadline_(true)
85 {
86 6x }
87
88 /// Construct by transferring state from `other`.
89 // Only moved before await_suspend; wait_ is engaged after.
90 12458x 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 8400x bool await_ready() const noexcept
100 {
101 8400x return false;
102 }
103
104 /// Resume inline if stopped or elapsed; else wait on a timer.
105 std::coroutine_handle<>
106 10430x await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
107 {
108 10430x if(env->stop_token.stop_requested())
109 {
110 4003x canceled_ = true;
111 4003x return h;
112 }
113
114 // Elapsed deadlines complete synchronously, but only once a
115 // pending stop request has already been ruled out above.
116 12850x if(has_deadline_ ?
117 6427x deadline_ <= std::chrono::steady_clock::now() :
118 6423x dur_.count() <= 0)
119 8x 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 6419x timer_.emplace(env->executor.context());
127 }
128 2x catch(std::logic_error const&)
129 {
130 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 }
137
138 6417x if(has_deadline_)
139 2x timer_->expires_at(deadline_);
140 else
141 6415x timer_->expires_after(dur_);
142
143 6417x wait_.emplace(timer_->wait());
144 6417x return wait_->await_suspend(h, env);
145 }
146
147 /// Return empty on expiry, `error::canceled` if stop won.
148 10406x capy::io_result<> await_resume() noexcept
149 {
150 10406x if(canceled_)
151 4003x return {capy::error::canceled};
152 6403x if(wait_)
153 6395x return wait_->await_resume();
154 8x 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 10422x 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 18792x auto ns = dsec(dur) >= dsec((nanoseconds::max)())
183 18792x ? (nanoseconds::max)()
184 10420x : dsec(dur) <= dsec((nanoseconds::min)())
185 10420x ? (nanoseconds::min)()
186 10418x : duration_cast<nanoseconds>(dur);
187 10422x 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