include/boost/capy/delay.hpp

100.0% Lines (54/54) 100.0% List of functions (10/10)
delay.hpp
f(x) Functions (10)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
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/capy
8 //
9
10 #ifndef BOOST_CAPY_DELAY_HPP
11 #define BOOST_CAPY_DELAY_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/continuation.hpp>
15 #include <boost/capy/error.hpp>
16 #include <boost/capy/ex/executor_ref.hpp>
17 #include <boost/capy/ex/io_env.hpp>
18 #include <boost/capy/ex/detail/timer_service.hpp>
19 #include <boost/capy/io_result.hpp>
20
21 #include <atomic>
22 #include <chrono>
23 #include <coroutine>
24 #include <new>
25 #include <stop_token>
26 #include <utility>
27
28 namespace boost {
29 namespace capy {
30
31 /** IoAwaitable returned by @ref delay.
32
33 Suspends the calling coroutine until the deadline elapses
34 or the environment's stop token is activated, whichever
35 comes first. Resumption is always posted through the
36 executor, never inline on the timer thread.
37
38 Not intended to be named directly; use the @ref delay
39 factory function instead.
40
41 @par Return Value
42
43 Returns `io_result<>{}` (no error) when the timer fires
44 normally, or `io_result<>{error::canceled}` when
45 cancellation claims the resume before the deadline.
46
47 @par Cancellation
48
49 If `stop_requested()` is true before suspension, the
50 coroutine resumes immediately without scheduling a timer
51 and returns `io_result<>{error::canceled}`. If stop is
52 requested while suspended, the stop callback claims the
53 resume and posts it through the executor; the pending
54 timer is cancelled on the next `await_resume` or
55 destructor call.
56
57 @par Thread Safety
58
59 A single `delay_awaitable` must not be awaited concurrently.
60 Multiple independent `delay()` calls on the same
61 execution_context are safe and share one timer thread.
62
63 @see delay, timeout
64 */
65 class delay_awaitable
66 {
67 std::chrono::nanoseconds dur_;
68
69 detail::timer_service* ts_ = nullptr;
70 detail::timer_service::timer_id tid_ = 0;
71
72 // Declared before stop_cb_buf_: the callback
73 // accesses these members, so they must still be
74 // alive if the stop_cb_ destructor blocks.
75 continuation cont_;
76 std::atomic<bool> claimed_{false};
77 bool canceled_ = false;
78 bool stop_cb_active_ = false;
79
80 struct cancel_fn
81 {
82 delay_awaitable* self_;
83 executor_ref ex_;
84
85 2x void operator()() const noexcept
86 {
87 2x if(!self_->claimed_.exchange(
88 true, std::memory_order_acq_rel))
89 {
90 2x self_->canceled_ = true;
91 2x ex_.post(self_->cont_);
92 }
93 2x }
94 };
95
96 using stop_cb_t = std::stop_callback<cancel_fn>;
97
98 // Aligned storage for the stop callback.
99 // Declared last: its destructor may block while
100 // the callback accesses the members above.
101 BOOST_CAPY_MSVC_WARNING_PUSH
102 BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
103 alignas(stop_cb_t)
104 unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
105 BOOST_CAPY_MSVC_WARNING_POP
106
107 20x stop_cb_t& stop_cb_() noexcept
108 {
109 20x return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
110 }
111
112 public:
113 29x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
114 29x : dur_(dur)
115 {
116 29x }
117
118 /// @pre The stop callback must not be active
119 /// (i.e. the object has not yet been awaited).
120 46x delay_awaitable(delay_awaitable&& o) noexcept
121 46x : dur_(o.dur_)
122 46x , ts_(o.ts_)
123 46x , tid_(o.tid_)
124 46x , cont_(o.cont_)
125 46x , claimed_(o.claimed_.load(std::memory_order_relaxed))
126 46x , canceled_(o.canceled_)
127 46x , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
128 {
129 46x }
130
131 75x ~delay_awaitable()
132 {
133 75x if(stop_cb_active_)
134 2x stop_cb_().~stop_cb_t();
135 75x if(ts_)
136 20x ts_->cancel(tid_);
137 75x }
138
139 delay_awaitable(delay_awaitable const&) = delete;
140 delay_awaitable& operator=(delay_awaitable const&) = delete;
141 delay_awaitable& operator=(delay_awaitable&&) = delete;
142
143 28x bool await_ready() const noexcept
144 {
145 28x return dur_.count() <= 0;
146 }
147
148 std::coroutine_handle<>
149 26x await_suspend(
150 std::coroutine_handle<> h,
151 io_env const* env) noexcept
152 {
153 // Already stopped: resume immediately
154 26x if(env->stop_token.stop_requested())
155 {
156 6x canceled_ = true;
157 6x return h;
158 }
159
160 20x cont_.h = h;
161 20x ts_ = &env->executor.context().use_service<detail::timer_service>();
162
163 // Schedule timer (won't fire inline since deadline is in the future)
164 20x tid_ = ts_->schedule_after(dur_,
165 20x [this, ex = env->executor]()
166 {
167 17x if(!claimed_.exchange(
168 true, std::memory_order_acq_rel))
169 {
170 17x ex.post(cont_);
171 }
172 17x });
173
174 // Register stop callback (may fire inline)
175 60x ::new(stop_cb_buf_) stop_cb_t(
176 20x env->stop_token,
177 20x cancel_fn{this, env->executor});
178 20x stop_cb_active_ = true;
179
180 20x return std::noop_coroutine();
181 }
182
183 27x io_result<> await_resume() noexcept
184 {
185 27x if(stop_cb_active_)
186 {
187 18x stop_cb_().~stop_cb_t();
188 18x stop_cb_active_ = false;
189 }
190 27x if(ts_)
191 18x ts_->cancel(tid_);
192 27x if(canceled_)
193 7x return io_result<>{make_error_code(error::canceled)};
194 20x return io_result<>{};
195 }
196 };
197
198 /** Suspend the current coroutine for a duration.
199
200 Returns an IoAwaitable that completes at or after the
201 specified duration, or earlier if the environment's stop
202 token is activated.
203
204 Zero or negative durations complete synchronously without
205 scheduling a timer.
206
207 @par Example
208 @code
209 auto [ec] = co_await delay(std::chrono::milliseconds(100));
210 @endcode
211
212 @param dur The duration to wait.
213
214 @return A @ref delay_awaitable whose `await_resume`
215 returns `io_result<>`. On normal completion, `ec`
216 is clear. On cancellation, `ec == error::canceled`.
217
218 @throws Nothing.
219
220 @see timeout, delay_awaitable
221 */
222 template<typename Rep, typename Period>
223 delay_awaitable
224 28x delay(std::chrono::duration<Rep, Period> dur) noexcept
225 {
226 return delay_awaitable{
227 28x std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
228 }
229
230 } // capy
231 } // boost
232
233 #endif
234