1  
//
1  
//
2  
// Copyright (c) 2026 Michael Vandeberg
2  
// Copyright (c) 2026 Michael Vandeberg
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_DELAY_HPP
10  
#ifndef BOOST_CAPY_DELAY_HPP
11  
#define BOOST_CAPY_DELAY_HPP
11  
#define BOOST_CAPY_DELAY_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/continuation.hpp>
14  
#include <boost/capy/continuation.hpp>
15  
#include <boost/capy/error.hpp>
15  
#include <boost/capy/error.hpp>
16  
#include <boost/capy/ex/executor_ref.hpp>
16  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/io_env.hpp>
17  
#include <boost/capy/ex/io_env.hpp>
18  
#include <boost/capy/ex/detail/timer_service.hpp>
18  
#include <boost/capy/ex/detail/timer_service.hpp>
19  
#include <boost/capy/io_result.hpp>
19  
#include <boost/capy/io_result.hpp>
20  

20  

21  
#include <atomic>
21  
#include <atomic>
22  
#include <chrono>
22  
#include <chrono>
23  
#include <coroutine>
23  
#include <coroutine>
24  
#include <new>
24  
#include <new>
25  
#include <stop_token>
25  
#include <stop_token>
26  
#include <utility>
26  
#include <utility>
27  

27  

28  
namespace boost {
28  
namespace boost {
29  
namespace capy {
29  
namespace capy {
30  

30  

31  
/** IoAwaitable returned by @ref delay.
31  
/** IoAwaitable returned by @ref delay.
32  

32  

33  
    Suspends the calling coroutine until the deadline elapses
33  
    Suspends the calling coroutine until the deadline elapses
34  
    or the environment's stop token is activated, whichever
34  
    or the environment's stop token is activated, whichever
35  
    comes first. Resumption is always posted through the
35  
    comes first. Resumption is always posted through the
36  
    executor, never inline on the timer thread.
36  
    executor, never inline on the timer thread.
37  

37  

38  
    Not intended to be named directly; use the @ref delay
38  
    Not intended to be named directly; use the @ref delay
39  
    factory function instead.
39  
    factory function instead.
40  

40  

41  
    @par Return Value
41  
    @par Return Value
42  

42  

43  
    Returns `io_result<>{}` (no error) when the timer fires
43  
    Returns `io_result<>{}` (no error) when the timer fires
44  
    normally, or `io_result<>{error::canceled}` when
44  
    normally, or `io_result<>{error::canceled}` when
45  
    cancellation claims the resume before the deadline.
45  
    cancellation claims the resume before the deadline.
46  

46  

47  
    @par Cancellation
47  
    @par Cancellation
48  

48  

49  
    If `stop_requested()` is true before suspension, the
49  
    If `stop_requested()` is true before suspension, the
50  
    coroutine resumes immediately without scheduling a timer
50  
    coroutine resumes immediately without scheduling a timer
51  
    and returns `io_result<>{error::canceled}`. If stop is
51  
    and returns `io_result<>{error::canceled}`. If stop is
52  
    requested while suspended, the stop callback claims the
52  
    requested while suspended, the stop callback claims the
53  
    resume and posts it through the executor; the pending
53  
    resume and posts it through the executor; the pending
54  
    timer is cancelled on the next `await_resume` or
54  
    timer is cancelled on the next `await_resume` or
55  
    destructor call.
55  
    destructor call.
56  

56  

57  
    @par Thread Safety
57  
    @par Thread Safety
58  

58  

59  
    A single `delay_awaitable` must not be awaited concurrently.
59  
    A single `delay_awaitable` must not be awaited concurrently.
60  
    Multiple independent `delay()` calls on the same
60  
    Multiple independent `delay()` calls on the same
61  
    execution_context are safe and share one timer thread.
61  
    execution_context are safe and share one timer thread.
62  

62  

63  
    @see delay, timeout
63  
    @see delay, timeout
64  
*/
64  
*/
65  
class delay_awaitable
65  
class delay_awaitable
66  
{
66  
{
67  
    std::chrono::nanoseconds dur_;
67  
    std::chrono::nanoseconds dur_;
68  

68  

69  
    detail::timer_service* ts_ = nullptr;
69  
    detail::timer_service* ts_ = nullptr;
70  
    detail::timer_service::timer_id tid_ = 0;
70  
    detail::timer_service::timer_id tid_ = 0;
71  

71  

72  
    // Declared before stop_cb_buf_: the callback
72  
    // Declared before stop_cb_buf_: the callback
73  
    // accesses these members, so they must still be
73  
    // accesses these members, so they must still be
74  
    // alive if the stop_cb_ destructor blocks.
74  
    // alive if the stop_cb_ destructor blocks.
75  
    continuation cont_;
75  
    continuation cont_;
76  
    std::atomic<bool> claimed_{false};
76  
    std::atomic<bool> claimed_{false};
77  
    bool canceled_ = false;
77  
    bool canceled_ = false;
78  
    bool stop_cb_active_ = false;
78  
    bool stop_cb_active_ = false;
79  

79  

80  
    struct cancel_fn
80  
    struct cancel_fn
81  
    {
81  
    {
82  
        delay_awaitable* self_;
82  
        delay_awaitable* self_;
83  
        executor_ref ex_;
83  
        executor_ref ex_;
84  

84  

85  
        void operator()() const noexcept
85  
        void operator()() const noexcept
86  
        {
86  
        {
87  
            if(!self_->claimed_.exchange(
87  
            if(!self_->claimed_.exchange(
88  
                true, std::memory_order_acq_rel))
88  
                true, std::memory_order_acq_rel))
89  
            {
89  
            {
90  
                self_->canceled_ = true;
90  
                self_->canceled_ = true;
91  
                ex_.post(self_->cont_);
91  
                ex_.post(self_->cont_);
92  
            }
92  
            }
93  
        }
93  
        }
94  
    };
94  
    };
95  

95  

96  
    using stop_cb_t = std::stop_callback<cancel_fn>;
96  
    using stop_cb_t = std::stop_callback<cancel_fn>;
97  

97  

98  
    // Aligned storage for the stop callback.
98  
    // Aligned storage for the stop callback.
99  
    // Declared last: its destructor may block while
99  
    // Declared last: its destructor may block while
100  
    // the callback accesses the members above.
100  
    // the callback accesses the members above.
101  
    BOOST_CAPY_MSVC_WARNING_PUSH
101  
    BOOST_CAPY_MSVC_WARNING_PUSH
102  
    BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
102  
    BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
103  
    alignas(stop_cb_t)
103  
    alignas(stop_cb_t)
104  
        unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
104  
        unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
105  
    BOOST_CAPY_MSVC_WARNING_POP
105  
    BOOST_CAPY_MSVC_WARNING_POP
106  

106  

107  
    stop_cb_t& stop_cb_() noexcept
107  
    stop_cb_t& stop_cb_() noexcept
108  
    {
108  
    {
109  
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
109  
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
110  
    }
110  
    }
111  

111  

112  
public:
112  
public:
113  
    explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
113  
    explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
114  
        : dur_(dur)
114  
        : dur_(dur)
115  
    {
115  
    {
116  
    }
116  
    }
117  

117  

118  
    /// @pre The stop callback must not be active
118  
    /// @pre The stop callback must not be active
119  
    ///      (i.e. the object has not yet been awaited).
119  
    ///      (i.e. the object has not yet been awaited).
120  
    delay_awaitable(delay_awaitable&& o) noexcept
120  
    delay_awaitable(delay_awaitable&& o) noexcept
121  
        : dur_(o.dur_)
121  
        : dur_(o.dur_)
122  
        , ts_(o.ts_)
122  
        , ts_(o.ts_)
123  
        , tid_(o.tid_)
123  
        , tid_(o.tid_)
124  
        , cont_(o.cont_)
124  
        , cont_(o.cont_)
125  
        , claimed_(o.claimed_.load(std::memory_order_relaxed))
125  
        , claimed_(o.claimed_.load(std::memory_order_relaxed))
126  
        , canceled_(o.canceled_)
126  
        , canceled_(o.canceled_)
127  
        , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
127  
        , stop_cb_active_(std::exchange(o.stop_cb_active_, false))
128  
    {
128  
    {
129  
    }
129  
    }
130  

130  

131  
    ~delay_awaitable()
131  
    ~delay_awaitable()
132  
    {
132  
    {
133  
        if(stop_cb_active_)
133  
        if(stop_cb_active_)
134  
            stop_cb_().~stop_cb_t();
134  
            stop_cb_().~stop_cb_t();
135  
        if(ts_)
135  
        if(ts_)
136  
            ts_->cancel(tid_);
136  
            ts_->cancel(tid_);
137  
    }
137  
    }
138  

138  

139  
    delay_awaitable(delay_awaitable const&) = delete;
139  
    delay_awaitable(delay_awaitable const&) = delete;
140  
    delay_awaitable& operator=(delay_awaitable const&) = delete;
140  
    delay_awaitable& operator=(delay_awaitable const&) = delete;
141  
    delay_awaitable& operator=(delay_awaitable&&) = delete;
141  
    delay_awaitable& operator=(delay_awaitable&&) = delete;
142  

142  

143  
    bool await_ready() const noexcept
143  
    bool await_ready() const noexcept
144  
    {
144  
    {
145  
        return dur_.count() <= 0;
145  
        return dur_.count() <= 0;
146  
    }
146  
    }
147  

147  

148  
    std::coroutine_handle<>
148  
    std::coroutine_handle<>
149  
    await_suspend(
149  
    await_suspend(
150  
        std::coroutine_handle<> h,
150  
        std::coroutine_handle<> h,
151  
        io_env const* env) noexcept
151  
        io_env const* env) noexcept
152  
    {
152  
    {
153  
        // Already stopped: resume immediately
153  
        // Already stopped: resume immediately
154  
        if(env->stop_token.stop_requested())
154  
        if(env->stop_token.stop_requested())
155  
        {
155  
        {
156  
            canceled_ = true;
156  
            canceled_ = true;
157  
            return h;
157  
            return h;
158  
        }
158  
        }
159  

159  

160  
        cont_.h = h;
160  
        cont_.h = h;
161  
        ts_ = &env->executor.context().use_service<detail::timer_service>();
161  
        ts_ = &env->executor.context().use_service<detail::timer_service>();
162  

162  

163  
        // Schedule timer (won't fire inline since deadline is in the future)
163  
        // Schedule timer (won't fire inline since deadline is in the future)
164  
        tid_ = ts_->schedule_after(dur_,
164  
        tid_ = ts_->schedule_after(dur_,
165  
            [this, ex = env->executor]()
165  
            [this, ex = env->executor]()
166  
            {
166  
            {
167  
                if(!claimed_.exchange(
167  
                if(!claimed_.exchange(
168  
                    true, std::memory_order_acq_rel))
168  
                    true, std::memory_order_acq_rel))
169  
                {
169  
                {
170  
                    ex.post(cont_);
170  
                    ex.post(cont_);
171  
                }
171  
                }
172  
            });
172  
            });
173  

173  

174  
        // Register stop callback (may fire inline)
174  
        // Register stop callback (may fire inline)
175  
        ::new(stop_cb_buf_) stop_cb_t(
175  
        ::new(stop_cb_buf_) stop_cb_t(
176  
            env->stop_token,
176  
            env->stop_token,
177  
            cancel_fn{this, env->executor});
177  
            cancel_fn{this, env->executor});
178  
        stop_cb_active_ = true;
178  
        stop_cb_active_ = true;
179  

179  

180  
        return std::noop_coroutine();
180  
        return std::noop_coroutine();
181  
    }
181  
    }
182  

182  

183  
    io_result<> await_resume() noexcept
183  
    io_result<> await_resume() noexcept
184  
    {
184  
    {
185  
        if(stop_cb_active_)
185  
        if(stop_cb_active_)
186  
        {
186  
        {
187  
            stop_cb_().~stop_cb_t();
187  
            stop_cb_().~stop_cb_t();
188  
            stop_cb_active_ = false;
188  
            stop_cb_active_ = false;
189  
        }
189  
        }
190  
        if(ts_)
190  
        if(ts_)
191  
            ts_->cancel(tid_);
191  
            ts_->cancel(tid_);
192  
        if(canceled_)
192  
        if(canceled_)
193  
            return io_result<>{make_error_code(error::canceled)};
193  
            return io_result<>{make_error_code(error::canceled)};
194  
        return io_result<>{};
194  
        return io_result<>{};
195  
    }
195  
    }
196  
};
196  
};
197  

197  

198  
/** Suspend the current coroutine for a duration.
198  
/** Suspend the current coroutine for a duration.
199  

199  

200  
    Returns an IoAwaitable that completes at or after the
200  
    Returns an IoAwaitable that completes at or after the
201  
    specified duration, or earlier if the environment's stop
201  
    specified duration, or earlier if the environment's stop
202  
    token is activated.
202  
    token is activated.
203  

203  

204  
    Zero or negative durations complete synchronously without
204  
    Zero or negative durations complete synchronously without
205  
    scheduling a timer.
205  
    scheduling a timer.
206  

206  

207  
    @par Example
207  
    @par Example
208  
    @code
208  
    @code
209  
    auto [ec] = co_await delay(std::chrono::milliseconds(100));
209  
    auto [ec] = co_await delay(std::chrono::milliseconds(100));
210  
    @endcode
210  
    @endcode
211  

211  

212  
    @param dur The duration to wait.
212  
    @param dur The duration to wait.
213  

213  

214  
    @return A @ref delay_awaitable whose `await_resume`
214  
    @return A @ref delay_awaitable whose `await_resume`
215  
        returns `io_result<>`. On normal completion, `ec`
215  
        returns `io_result<>`. On normal completion, `ec`
216  
        is clear. On cancellation, `ec == error::canceled`.
216  
        is clear. On cancellation, `ec == error::canceled`.
217  

217  

218  
    @throws Nothing.
218  
    @throws Nothing.
219  

219  

220  
    @see timeout, delay_awaitable
220  
    @see timeout, delay_awaitable
221  
*/
221  
*/
222  
template<typename Rep, typename Period>
222  
template<typename Rep, typename Period>
223  
delay_awaitable
223  
delay_awaitable
224  
delay(std::chrono::duration<Rep, Period> dur) noexcept
224  
delay(std::chrono::duration<Rep, Period> dur) noexcept
225  
{
225  
{
226  
    return delay_awaitable{
226  
    return delay_awaitable{
227  
        std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
227  
        std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
228  
}
228  
}
229  

229  

230  
} // capy
230  
} // capy
231  
} // boost
231  
} // boost
232  

232  

233  
#endif
233  
#endif