TLA Line data 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 HIT 1 : void operator()() const noexcept
76 : {
77 1 : if(!self_->claimed_.exchange(
78 : true, std::memory_order_acq_rel))
79 : {
80 1 : self_->canceled_ = true;
81 1 : ex_.post(h_);
82 : }
83 1 : }
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 19 : stop_cb_t& stop_cb_() noexcept
102 : {
103 19 : return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
104 : }
105 :
106 : public:
107 27 : explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
108 27 : : dur_(dur)
109 : {
110 27 : }
111 :
112 : /// @pre The stop callback must not be active
113 : /// (i.e. the object has not yet been awaited).
114 58 : delay_awaitable(delay_awaitable&& o) noexcept
115 58 : : dur_(o.dur_)
116 58 : , ts_(o.ts_)
117 58 : , tid_(o.tid_)
118 58 : , claimed_(o.claimed_.load(std::memory_order_relaxed))
119 58 : , canceled_(o.canceled_)
120 58 : , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
121 : {
122 58 : }
123 :
124 85 : ~delay_awaitable()
125 : {
126 85 : if(stop_cb_active_)
127 1 : stop_cb_().~stop_cb_t();
128 85 : if(ts_)
129 19 : ts_->cancel(tid_);
130 85 : }
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 26 : bool await_ready() const noexcept
137 : {
138 26 : return dur_.count() <= 0;
139 : }
140 :
141 : std::coroutine_handle<>
142 24 : await_suspend(
143 : std::coroutine_handle<> h,
144 : io_env const* env) noexcept
145 : {
146 : // Already stopped: resume immediately
147 24 : if(env->stop_token.stop_requested())
148 : {
149 5 : canceled_ = true;
150 5 : return h;
151 : }
152 :
153 19 : ts_ = &env->executor.context().use_service<detail::timer_service>();
154 :
155 : // Register stop callback before arming the timer.
156 : // Once the timer is armed, another thread can fire it,
157 : // resume the coroutine, and destroy this awaitable.
158 19 : stop_cb_active_ = true;
159 57 : ::new(stop_cb_buf_) stop_cb_t(
160 19 : env->stop_token,
161 19 : cancel_fn{this, env->executor, h});
162 :
163 : // If the stop callback already claimed the resume
164 : // (inline invocation), skip the timer entirely.
165 19 : if(claimed_.load(std::memory_order_acquire))
166 MIS 0 : return std::noop_coroutine();
167 :
168 : // Schedule timer using the output-reference overload so
169 : // that tid_ is written while the timer_service lock is
170 : // held — the timer thread cannot fire the callback until
171 : // after the lock is released, at which point tid_ is set.
172 HIT 19 : ts_->schedule_after(dur_,
173 55 : [this, h, ex = env->executor]()
174 : {
175 17 : if(!claimed_.exchange(
176 : true, std::memory_order_acq_rel))
177 : {
178 17 : ex.post(h);
179 : }
180 17 : },
181 19 : tid_);
182 :
183 19 : return std::noop_coroutine();
184 : }
185 :
186 26 : void await_resume() noexcept
187 : {
188 26 : if(stop_cb_active_)
189 : {
190 18 : stop_cb_().~stop_cb_t();
191 18 : stop_cb_active_ = false;
192 : }
193 26 : if(ts_)
194 18 : ts_->cancel(tid_);
195 26 : }
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. Completion is always normal (void
203 : return); no exception is thrown on cancellation.
204 :
205 : Zero or negative durations complete synchronously without
206 : scheduling a timer.
207 :
208 : @par Example
209 : @code
210 : co_await delay(std::chrono::milliseconds(100));
211 : @endcode
212 :
213 : @param dur The duration to wait.
214 :
215 : @return A @ref delay_awaitable whose `await_resume`
216 : returns `void`.
217 :
218 : @throws Nothing.
219 :
220 : @see timeout, delay_awaitable
221 : */
222 : template<typename Rep, typename Period>
223 : delay_awaitable
224 26 : delay(std::chrono::duration<Rep, Period> dur) noexcept
225 : {
226 : return delay_awaitable{
227 26 : std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
228 : }
229 :
230 : } // capy
231 : } // boost
232 :
233 : #endif
|