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 -
        void operator()() const noexcept
 
76 -
        {
 
77 -
            if(!self_->claimed_.exchange(
 
78 -
                true, std::memory_order_acq_rel))
 
79 -
            {
 
80 -
                self_->canceled_ = true;
 
81 -
                ex_.post(h_);
 
82 -
            }
 
83 -
        }
 
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 -
    stop_cb_t& stop_cb_() noexcept
 
102 -
    {
 
103 -
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
 
104 -
    }
 
105 -

 
106 -
public:
 
107 -
    explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
 
108 -
        : dur_(dur)
 
109 -
    {
 
110 -
    }
 
111 -

 
112 -
    /// @pre The stop callback must not be active
 
113 -
    ///      (i.e. the object has not yet been awaited).
 
114 -
    delay_awaitable(delay_awaitable&& o) noexcept
 
115 -
        : dur_(o.dur_)
 
116 -
        , ts_(o.ts_)
 
117 -
        , tid_(o.tid_)
 
118 -
        , claimed_(o.claimed_.load(std::memory_order_relaxed))
 
119 -
        , canceled_(o.canceled_)
 
120 -
        , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
 
121 -
    {
 
122 -
    }
 
123 -

 
124 -
    ~delay_awaitable()
 
125 -
    {
 
126 -
        if(stop_cb_active_)
 
127 -
            stop_cb_().~stop_cb_t();
 
128 -
        if(ts_)
 
129 -
            ts_->cancel(tid_);
 
130 -
    }
 
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 -
    bool await_ready() const noexcept
 
137 -
    {
 
138 -
        return dur_.count() <= 0;
 
139 -
    }
 
140 -

 
141 -
    std::coroutine_handle<>
 
142 -
    await_suspend(
 
143 -
        std::coroutine_handle<> h,
 
144 -
        io_env const* env) noexcept
 
145 -
    {
 
146 -
        // Already stopped: resume immediately
 
147 -
        if(env->stop_token.stop_requested())
 
148 -
        {
 
149 -
            canceled_ = true;
 
150 -
            return h;
 
151 -
        }
 
152 -

 
153 -
        ts_ = &env->executor.context().use_service<detail::timer_service>();
 
154 -

 
155 -
        // Schedule timer (won't fire inline since deadline is in the future)
 
156 -
        tid_ = ts_->schedule_after(dur_,
 
157 -
            [this, h, ex = env->executor]()
 
158 -
            {
 
159 -
                if(!claimed_.exchange(
 
160 -
                    true, std::memory_order_acq_rel))
 
161 -
                {
 
162 -
                    ex.post(h);
 
163 -
                }
 
164 -
            });
 
165 -

 
166 -
        // Register stop callback (may fire inline)
 
167 -
        ::new(stop_cb_buf_) stop_cb_t(
 
168 -
            env->stop_token,
 
169 -
            cancel_fn{this, env->executor, h});
 
170 -
        stop_cb_active_ = true;
 
171 -

 
172 -
        return std::noop_coroutine();
 
173 -
    }
 
174 -

 
175 -
    void await_resume() noexcept
 
176 -
    {
 
177 -
        if(stop_cb_active_)
 
178 -
        {
 
179 -
            stop_cb_().~stop_cb_t();
 
180 -
            stop_cb_active_ = false;
 
181 -
        }
 
182 -
        if(ts_)
 
183 -
            ts_->cancel(tid_);
 
184 -
    }
 
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 -
delay(std::chrono::duration<Rep, Period> dur) noexcept
 
214 -
{
 
215 -
    return delay_awaitable{
 
216 -
        std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
 
217 -
}
 
218 -

 
219 -
} // capy
 
220 -
} // boost
 
221 -

 
222 -
#endif