include/boost/capy/delay.hpp

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