include/boost/capy/delay.hpp

100.0% Lines (56/56) 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 28x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
114 28x : dur_(dur)
115 {
116 28x }
117
118 /// @pre The stop callback must not be active
119 /// (i.e. the object has not yet been awaited).
120 43x delay_awaitable(delay_awaitable&& o) noexcept
121 43x : dur_(o.dur_)
122 43x , ts_(o.ts_)
123 43x , tid_(o.tid_)
124 43x , cont_(o.cont_)
125 43x , claimed_(o.claimed_.load(std::memory_order_relaxed))
126 43x , canceled_(o.canceled_)
127 43x , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
128 {
129 43x }
130
131 71x ~delay_awaitable()
132 {
133 71x if(stop_cb_active_)
134 2x stop_cb_().~stop_cb_t();
135 71x if(ts_)
136 20x ts_->cancel(tid_);
137 71x }
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 27x bool await_ready() const noexcept
144 {
145 27x return dur_.count() <= 0;
146 }
147
148 std::coroutine_handle<>
149 25x await_suspend(
150 std::coroutine_handle<> h,
151 io_env const* env) noexcept
152 {
153 // Already stopped: resume immediately
154 25x if(env->stop_token.stop_requested())
155 {
156 5x canceled_ = true;
157 5x 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 26x io_result<> await_resume() noexcept
184 {
185 26x if(stop_cb_active_)
186 {
187 18x stop_cb_().~stop_cb_t();
188 18x stop_cb_active_ = false;
189 }
190 26x if(ts_)
191 18x ts_->cancel(tid_);
192 26x if(canceled_)
193 6x 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 27x delay(std::chrono::duration<Rep, Period> dur) noexcept
225 {
226 return delay_awaitable{
227 27x std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
228 }
229
230 } // capy
231 } // boost
232
233 #endif
234