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/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 HIT 1 : void operator()() const noexcept
85 : {
86 1 : if(!self_->claimed_.exchange(
87 : true, std::memory_order_acq_rel))
88 : {
89 1 : self_->canceled_ = true;
90 1 : ex_.post(h_);
91 : }
92 1 : }
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 19 : stop_cb_t& stop_cb_() noexcept
107 : {
108 19 : return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
109 : }
110 :
111 : public:
112 27 : explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
113 27 : : dur_(dur)
114 : {
115 27 : }
116 :
117 : /// @pre The stop callback must not be active
118 : /// (i.e. the object has not yet been awaited).
119 42 : delay_awaitable(delay_awaitable&& o) noexcept
120 42 : : dur_(o.dur_)
121 42 : , ts_(o.ts_)
122 42 : , tid_(o.tid_)
123 42 : , claimed_(o.claimed_.load(std::memory_order_relaxed))
124 42 : , canceled_(o.canceled_)
125 42 : , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
126 : {
127 42 : }
128 :
129 69 : ~delay_awaitable()
130 : {
131 69 : if(stop_cb_active_)
132 1 : stop_cb_().~stop_cb_t();
133 69 : if(ts_)
134 19 : ts_->cancel(tid_);
135 69 : }
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 26 : bool await_ready() const noexcept
142 : {
143 26 : return dur_.count() <= 0;
144 : }
145 :
146 : std::coroutine_handle<>
147 24 : await_suspend(
148 : std::coroutine_handle<> h,
149 : io_env const* env) noexcept
150 : {
151 : // Already stopped: resume immediately
152 24 : if(env->stop_token.stop_requested())
153 : {
154 5 : canceled_ = true;
155 5 : return h;
156 : }
157 :
158 19 : ts_ = &env->executor.context().use_service<detail::timer_service>();
159 :
160 : // Schedule timer (won't fire inline since deadline is in the future)
161 19 : tid_ = ts_->schedule_after(dur_,
162 19 : [this, h, ex = env->executor]()
163 : {
164 17 : if(!claimed_.exchange(
165 : true, std::memory_order_acq_rel))
166 : {
167 17 : ex.post(h);
168 : }
169 17 : });
170 :
171 : // Register stop callback (may fire inline)
172 57 : ::new(stop_cb_buf_) stop_cb_t(
173 19 : env->stop_token,
174 19 : cancel_fn{this, env->executor, h});
175 19 : stop_cb_active_ = true;
176 :
177 19 : return std::noop_coroutine();
178 : }
179 :
180 26 : io_result<> await_resume() noexcept
181 : {
182 26 : if(stop_cb_active_)
183 : {
184 18 : stop_cb_().~stop_cb_t();
185 18 : stop_cb_active_ = false;
186 : }
187 26 : if(ts_)
188 18 : ts_->cancel(tid_);
189 26 : if(canceled_)
190 6 : return io_result<>{make_error_code(error::canceled)};
191 20 : 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 26 : delay(std::chrono::duration<Rep, Period> dur) noexcept
222 : {
223 : return delay_awaitable{
224 26 : std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
225 : }
226 :
227 : } // capy
228 : } // boost
229 :
230 : #endif
|