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

19  

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

26  

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

29  

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

31  

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

36  

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

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 +

38  
    @par Cancellation
46  
    @par Cancellation
39  

47  

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

55  

47  
    @par Thread Safety
56  
    @par Thread Safety
48  

57  

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

61  

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

67  

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

70  

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

77  

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

83  

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

94  

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

96  

88  
    // Aligned storage for the stop callback.
97  
    // Aligned storage for the stop callback.
89  
    // Declared last: its destructor may block while
98  
    // Declared last: its destructor may block while
90  
    // the callback accesses the members above.
99  
    // the callback accesses the members above.
91  
    BOOST_CAPY_MSVC_WARNING_PUSH
100  
    BOOST_CAPY_MSVC_WARNING_PUSH
92  
    BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
101  
    BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
93  
    alignas(stop_cb_t)
102  
    alignas(stop_cb_t)
94  
        unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
103  
        unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
95  
    BOOST_CAPY_MSVC_WARNING_POP
104  
    BOOST_CAPY_MSVC_WARNING_POP
96  

105  

97  
    stop_cb_t& stop_cb_() noexcept
106  
    stop_cb_t& stop_cb_() noexcept
98  
    {
107  
    {
99  
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
108  
        return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
100  
    }
109  
    }
101  

110  

102  
public:
111  
public:
103  
    explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
112  
    explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
104  
        : dur_(dur)
113  
        : dur_(dur)
105  
    {
114  
    {
106  
    }
115  
    }
107  

116  

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

128  

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

136  

128  
    delay_awaitable(delay_awaitable const&) = delete;
137  
    delay_awaitable(delay_awaitable const&) = delete;
129  
    delay_awaitable& operator=(delay_awaitable const&) = delete;
138  
    delay_awaitable& operator=(delay_awaitable const&) = delete;
130  
    delay_awaitable& operator=(delay_awaitable&&) = delete;
139  
    delay_awaitable& operator=(delay_awaitable&&) = delete;
131  

140  

132  
    bool await_ready() const noexcept
141  
    bool await_ready() const noexcept
133  
    {
142  
    {
134  
        return dur_.count() <= 0;
143  
        return dur_.count() <= 0;
135  
    }
144  
    }
136  

145  

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

157  

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

159  

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

170  

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

176  

168  
        return std::noop_coroutine();
177  
        return std::noop_coroutine();
169  
    }
178  
    }
170  

179  

171 -
    void await_resume() noexcept
180 +
    io_result<> await_resume() noexcept
172  
    {
181  
    {
173  
        if(stop_cb_active_)
182  
        if(stop_cb_active_)
174  
        {
183  
        {
175  
            stop_cb_().~stop_cb_t();
184  
            stop_cb_().~stop_cb_t();
176  
            stop_cb_active_ = false;
185  
            stop_cb_active_ = false;
177  
        }
186  
        }
178  
        if(ts_)
187  
        if(ts_)
179  
            ts_->cancel(tid_);
188  
            ts_->cancel(tid_);
 
189 +
        if(canceled_)
 
190 +
            return io_result<>{make_error_code(error::canceled)};
 
191 +
        return io_result<>{};
180  
    }
192  
    }
181  
};
193  
};
182  

194  

183  
/** Suspend the current coroutine for a duration.
195  
/** Suspend the current coroutine for a duration.
184  

196  

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

200  

190  
    Zero or negative durations complete synchronously without
201  
    Zero or negative durations complete synchronously without
191  
    scheduling a timer.
202  
    scheduling a timer.
192  

203  

193  
    @par Example
204  
    @par Example
194  
    @code
205  
    @code
195 -
    co_await delay(std::chrono::milliseconds(100));
206 +
    auto [ec] = co_await delay(std::chrono::milliseconds(100));
196  
    @endcode
207  
    @endcode
197  

208  

198  
    @param dur The duration to wait.
209  
    @param dur The duration to wait.
199  

210  

200  
    @return A @ref delay_awaitable whose `await_resume`
211  
    @return A @ref delay_awaitable whose `await_resume`
201 -
        returns `void`.
212 +
        returns `io_result<>`. On normal completion, `ec`
 
213 +
        is clear. On cancellation, `ec == error::canceled`.
202  

214  

203  
    @throws Nothing.
215  
    @throws Nothing.
204  

216  

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

226  

215  
} // capy
227  
} // capy
216  
} // boost
228  
} // boost
217  

229  

218  
#endif
230  
#endif