include/boost/capy/delay.hpp

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