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 10x stop_cb_t& stop_cb_() noexcept
102 {
103 10x return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
104 }
105
106 public:
107 17x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
108 17x : dur_(dur)
109 {
110 17x }
111
112 /// @pre The stop callback must not be active
113 /// (i.e. the object has not yet been awaited).
114 48x delay_awaitable(delay_awaitable&& o) noexcept
115 48x : dur_(o.dur_)
116 48x , ts_(o.ts_)
117 48x , tid_(o.tid_)
118 48x , claimed_(o.claimed_.load(std::memory_order_relaxed))
119 48x , canceled_(o.canceled_)
120 48x , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
121 {
122 48x }
123
124 65x ~delay_awaitable()
125 {
126
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 64 times.
65x if(stop_cb_active_)
127 1x stop_cb_().~stop_cb_t();
128
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 55 times.
65x if(ts_)
129 10x ts_->cancel(tid_);
130 65x }
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 16x bool await_ready() const noexcept
137 {
138 16x return dur_.count() <= 0;
139 }
140
141 std::coroutine_handle<>
142 15x 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 10 times.
15x if(env->stop_token.stop_requested())
148 {
149 5x canceled_ = true;
150 5x return h;
151 }
152
153 10x ts_ = &env->executor.context().use_service<detail::timer_service>();
154
155 // Schedule timer (won't fire inline since deadline is in the future)
156 10x tid_ = ts_->schedule_after(dur_,
157 10x [this, h, ex = env->executor]()
158 {
159
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8x if(!claimed_.exchange(
160 true, std::memory_order_acq_rel))
161 {
162 8x ex.post(h);
163 }
164 8x });
165
166 // Register stop callback (may fire inline)
167 30x ::new(stop_cb_buf_) stop_cb_t(
168 10x env->stop_token,
169 10x cancel_fn{this, env->executor, h});
170 10x stop_cb_active_ = true;
171
172 10x return std::noop_coroutine();
173 }
174
175 16x void await_resume() noexcept
176 {
177
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 7 times.
16x if(stop_cb_active_)
178 {
179 9x stop_cb_().~stop_cb_t();
180 9x stop_cb_active_ = false;
181 }
182
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 7 times.
16x if(ts_)
183 9x ts_->cancel(tid_);
184 16x }
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 16x delay(std::chrono::duration<Rep, Period> dur) noexcept
214 {
215 return delay_awaitable{
216 16x std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
217 }
218
219 } // capy
220 } // boost
221
222 #endif
223