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/ex/executor_ref.hpp>
14  
#include <boost/capy/ex/executor_ref.hpp>
15  
#include <boost/capy/ex/io_env.hpp>
15  
#include <boost/capy/ex/io_env.hpp>
16  
#include <boost/capy/ex/detail/timer_service.hpp>
16  
#include <boost/capy/ex/detail/timer_service.hpp>
17  

17  

18  
#include <atomic>
18  
#include <atomic>
19  
#include <chrono>
19  
#include <chrono>
20  
#include <coroutine>
20  
#include <coroutine>
21  
#include <new>
21  
#include <new>
22  
#include <stop_token>
22  
#include <stop_token>
23  
#include <utility>
23  
#include <utility>
24  

24  

25  
namespace boost {
25  
namespace boost {
26  
namespace capy {
26  
namespace capy {
27  

27  

28  
/** IoAwaitable returned by @ref delay.
28  
/** IoAwaitable returned by @ref delay.
29  

29  

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

34  

35  
    Not intended to be named directly; use the @ref delay
35  
    Not intended to be named directly; use the @ref delay
36  
    factory function instead.
36  
    factory function instead.
37  

37  

38  
    @par Cancellation
38  
    @par Cancellation
39  

39  

40  
    If `stop_requested()` is true before suspension, the
40  
    If `stop_requested()` is true before suspension, the
41  
    coroutine resumes immediately without scheduling a timer.
41  
    coroutine resumes immediately without scheduling a timer.
42  
    If stop is requested while suspended, the stop callback
42  
    If stop is requested while suspended, the stop callback
43  
    claims the resume and posts it through the executor; the
43  
    claims the resume and posts it through the executor; the
44  
    pending timer is cancelled on the next `await_resume` or
44  
    pending timer is cancelled on the next `await_resume` or
45  
    destructor call.
45  
    destructor call.
46  

46  

47  
    @par Thread Safety
47  
    @par Thread Safety
48  

48  

49  
    A single `delay_awaitable` must not be awaited concurrently.
49  
    A single `delay_awaitable` must not be awaited concurrently.
50  
    Multiple independent `delay()` calls on the same
50  
    Multiple independent `delay()` calls on the same
51  
    execution_context are safe and share one timer thread.
51  
    execution_context are safe and share one timer thread.
52  

52  

53  
    @see delay, timeout
53  
    @see delay, timeout
54  
*/
54  
*/
55  
class delay_awaitable
55  
class delay_awaitable
56  
{
56  
{
57  
    std::chrono::nanoseconds dur_;
57  
    std::chrono::nanoseconds dur_;
58  

58  

59  
    detail::timer_service* ts_ = nullptr;
59  
    detail::timer_service* ts_ = nullptr;
60  
    detail::timer_service::timer_id tid_ = 0;
60  
    detail::timer_service::timer_id tid_ = 0;
61  

61  

62  
    // Declared before stop_cb_buf_: the callback
62  
    // Declared before stop_cb_buf_: the callback
63  
    // accesses these members, so they must still be
63  
    // accesses these members, so they must still be
64  
    // alive if the stop_cb_ destructor blocks.
64  
    // alive if the stop_cb_ destructor blocks.
65  
    std::atomic<bool> claimed_{false};
65  
    std::atomic<bool> claimed_{false};
66  
    bool canceled_ = false;
66  
    bool canceled_ = false;
67  
    bool stop_cb_active_ = false;
67  
    bool stop_cb_active_ = false;
68  

68  

69  
    struct cancel_fn
69  
    struct cancel_fn
70  
    {
70  
    {
71  
        delay_awaitable* self_;
71  
        delay_awaitable* self_;
72  
        executor_ref ex_;
72  
        executor_ref ex_;
73  
        std::coroutine_handle<> h_;
73  
        std::coroutine_handle<> h_;
74  

74  

75  
        void operator()() const noexcept
75  
        void operator()() const noexcept
76  
        {
76  
        {
77  
            if(!self_->claimed_.exchange(
77  
            if(!self_->claimed_.exchange(
78  
                true, std::memory_order_acq_rel))
78  
                true, std::memory_order_acq_rel))
79  
            {
79  
            {
80  
                self_->canceled_ = true;
80  
                self_->canceled_ = true;
81  
                ex_.post(h_);
81  
                ex_.post(h_);
82  
            }
82  
            }
83  
        }
83  
        }
84  
    };
84  
    };
85  

85  

86  
    using stop_cb_t = std::stop_callback<cancel_fn>;
86  
    using stop_cb_t = std::stop_callback<cancel_fn>;
87  

87  

88  
    // Aligned storage for the stop callback.
88  
    // Aligned storage for the stop callback.
89  
    // Declared last: its destructor may block while
89  
    // Declared last: its destructor may block while
90  
    // the callback accesses the members above.
90  
    // the callback accesses the members above.
91 -
#ifdef _MSC_VER
91 +
    BOOST_CAPY_MSVC_WARNING_PUSH
92 -
# pragma warning(push)
92 +
    BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
93 -
# pragma warning(disable: 4324)
 
94 -
#endif
 
95  
    alignas(stop_cb_t)
93  
    alignas(stop_cb_t)
96  
        unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
94  
        unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
97 -
#ifdef _MSC_VER
95 +
    BOOST_CAPY_MSVC_WARNING_POP
98 -
# pragma warning(pop)
 
99 -
#endif
 
100  

96  

101  
    stop_cb_t& stop_cb_() noexcept
97  
    stop_cb_t& stop_cb_() noexcept
102  
    {
98  
    {
103  
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
99  
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
104  
    }
100  
    }
105  

101  

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

107  

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

119  

124  
    ~delay_awaitable()
120  
    ~delay_awaitable()
125  
    {
121  
    {
126  
        if(stop_cb_active_)
122  
        if(stop_cb_active_)
127  
            stop_cb_().~stop_cb_t();
123  
            stop_cb_().~stop_cb_t();
128  
        if(ts_)
124  
        if(ts_)
129  
            ts_->cancel(tid_);
125  
            ts_->cancel(tid_);
130  
    }
126  
    }
131  

127  

132  
    delay_awaitable(delay_awaitable const&) = delete;
128  
    delay_awaitable(delay_awaitable const&) = delete;
133  
    delay_awaitable& operator=(delay_awaitable const&) = delete;
129  
    delay_awaitable& operator=(delay_awaitable const&) = delete;
134  
    delay_awaitable& operator=(delay_awaitable&&) = delete;
130  
    delay_awaitable& operator=(delay_awaitable&&) = delete;
135  

131  

136  
    bool await_ready() const noexcept
132  
    bool await_ready() const noexcept
137  
    {
133  
    {
138  
        return dur_.count() <= 0;
134  
        return dur_.count() <= 0;
139  
    }
135  
    }
140  

136  

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

148  

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

150  

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

161  

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

167  

172  
        return std::noop_coroutine();
168  
        return std::noop_coroutine();
173  
    }
169  
    }
174  

170  

175  
    void await_resume() noexcept
171  
    void await_resume() noexcept
176  
    {
172  
    {
177  
        if(stop_cb_active_)
173  
        if(stop_cb_active_)
178  
        {
174  
        {
179  
            stop_cb_().~stop_cb_t();
175  
            stop_cb_().~stop_cb_t();
180  
            stop_cb_active_ = false;
176  
            stop_cb_active_ = false;
181  
        }
177  
        }
182  
        if(ts_)
178  
        if(ts_)
183  
            ts_->cancel(tid_);
179  
            ts_->cancel(tid_);
184  
    }
180  
    }
185  
};
181  
};
186  

182  

187  
/** Suspend the current coroutine for a duration.
183  
/** Suspend the current coroutine for a duration.
188  

184  

189  
    Returns an IoAwaitable that completes at or after the
185  
    Returns an IoAwaitable that completes at or after the
190  
    specified duration, or earlier if the environment's stop
186  
    specified duration, or earlier if the environment's stop
191  
    token is activated. Completion is always normal (void
187  
    token is activated. Completion is always normal (void
192  
    return); no exception is thrown on cancellation.
188  
    return); no exception is thrown on cancellation.
193  

189  

194  
    Zero or negative durations complete synchronously without
190  
    Zero or negative durations complete synchronously without
195  
    scheduling a timer.
191  
    scheduling a timer.
196  

192  

197  
    @par Example
193  
    @par Example
198  
    @code
194  
    @code
199  
    co_await delay(std::chrono::milliseconds(100));
195  
    co_await delay(std::chrono::milliseconds(100));
200  
    @endcode
196  
    @endcode
201  

197  

202  
    @param dur The duration to wait.
198  
    @param dur The duration to wait.
203  

199  

204  
    @return A @ref delay_awaitable whose `await_resume`
200  
    @return A @ref delay_awaitable whose `await_resume`
205  
        returns `void`.
201  
        returns `void`.
206  

202  

207  
    @throws Nothing.
203  
    @throws Nothing.
208  

204  

209  
    @see timeout, delay_awaitable
205  
    @see timeout, delay_awaitable
210  
*/
206  
*/
211  
template<typename Rep, typename Period>
207  
template<typename Rep, typename Period>
212  
delay_awaitable
208  
delay_awaitable
213  
delay(std::chrono::duration<Rep, Period> dur) noexcept
209  
delay(std::chrono::duration<Rep, Period> dur) noexcept
214  
{
210  
{
215  
    return delay_awaitable{
211  
    return delay_awaitable{
216  
        std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
212  
        std::chrono::duration_cast<std::chrono::nanoseconds>(dur)};
217  
}
213  
}
218  

214  

219  
} // capy
215  
} // capy
220  
} // boost
216  
} // boost
221  

217  

222  
#endif
218  
#endif