1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
// Copyright (c) 2026 Steve Gerbino
3  
// Copyright (c) 2026 Steve Gerbino
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
//
7  
//
8  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
9  
//
9  
//
10  

10  

11  
#ifndef BOOST_COROSIO_TIMER_HPP
11  
#ifndef BOOST_COROSIO_TIMER_HPP
12  
#define BOOST_COROSIO_TIMER_HPP
12  
#define BOOST_COROSIO_TIMER_HPP
13  

13  

14  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
15 -
#include <boost/corosio/io_object.hpp>
15 +
#include <boost/corosio/io/io_timer.hpp>
16 -
#include <boost/capy/io_result.hpp>
 
17 -
#include <boost/capy/error.hpp>
 
18 -
#include <boost/capy/ex/executor_ref.hpp>
 
19 -
#include <boost/capy/ex/io_env.hpp>
 
20  
#include <boost/capy/ex/execution_context.hpp>
16  
#include <boost/capy/ex/execution_context.hpp>
21 -
#include <system_error>
 
22  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/executor.hpp>
23  

18  

24 -
#include <coroutine>
 
25  
#include <chrono>
19  
#include <chrono>
26 -
#include <limits>
 
27 -
#include <stop_token>
 
28  
#include <cstddef>
20  
#include <cstddef>
29  

21  

30  
namespace boost::corosio {
22  
namespace boost::corosio {
31  

23  

32  
/** An asynchronous timer for coroutine I/O.
24  
/** An asynchronous timer for coroutine I/O.
33  

25  

34  
    This class provides asynchronous timer operations that return
26  
    This class provides asynchronous timer operations that return
35  
    awaitable types. The timer can be used to schedule operations
27  
    awaitable types. The timer can be used to schedule operations
36  
    to occur after a specified duration or at a specific time point.
28  
    to occur after a specified duration or at a specific time point.
37  

29  

38  
    Multiple coroutines may wait concurrently on the same timer.
30  
    Multiple coroutines may wait concurrently on the same timer.
39  
    When the timer expires, all waiters complete with success. When
31  
    When the timer expires, all waiters complete with success. When
40  
    the timer is cancelled, all waiters complete with an error that
32  
    the timer is cancelled, all waiters complete with an error that
41  
    compares equal to `capy::cond::canceled`.
33  
    compares equal to `capy::cond::canceled`.
42  

34  

43  
    Each timer operation participates in the affine awaitable protocol,
35  
    Each timer operation participates in the affine awaitable protocol,
44  
    ensuring coroutines resume on the correct executor.
36  
    ensuring coroutines resume on the correct executor.
45  

37  

46  
    @par Thread Safety
38  
    @par Thread Safety
47  
    Distinct objects: Safe.@n
39  
    Distinct objects: Safe.@n
48  
    Shared objects: Unsafe.
40  
    Shared objects: Unsafe.
49  

41  

50  
    @par Semantics
42  
    @par Semantics
51  
    Wraps platform timer facilities via the io_context reactor.
43  
    Wraps platform timer facilities via the io_context reactor.
52  
    Operations dispatch to OS timer APIs (timerfd, IOCP timers,
44  
    Operations dispatch to OS timer APIs (timerfd, IOCP timers,
53  
    kqueue EVFILT_TIMER).
45  
    kqueue EVFILT_TIMER).
54  
*/
46  
*/
55 -
class BOOST_COROSIO_DECL timer : public io_object
47 +
class BOOST_COROSIO_DECL timer : public io_timer
56 -
    struct wait_awaitable
 
57 -
    {
 
58 -
        timer& t_;
 
59 -
        std::stop_token token_;
 
60 -
        mutable std::error_code ec_;
 
61 -

 
62 -
        explicit wait_awaitable(timer& t) noexcept : t_(t) {}
 
63 -

 
64 -
        bool await_ready() const noexcept
 
65 -
        {
 
66 -
            return token_.stop_requested();
 
67 -
        }
 
68 -

 
69 -
        capy::io_result<> await_resume() const noexcept
 
70 -
        {
 
71 -
            if (token_.stop_requested())
 
72 -
                return {capy::error::canceled};
 
73 -
            return {ec_};
 
74 -
        }
 
75 -

 
76 -
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
 
77 -
            -> std::coroutine_handle<>
 
78 -
        {
 
79 -
            token_ = env->stop_token;
 
80 -
            auto& impl = t_.get();
 
81 -
            // Inline fast path: already expired and not in the heap
 
82 -
            if (impl.heap_index_ == implementation::npos &&
 
83 -
                (impl.expiry_ == (time_point::min)() ||
 
84 -
                 impl.expiry_ <= clock_type::now()))
 
85 -
            {
 
86 -
                ec_ = {};
 
87 -
                auto d = env->executor;
 
88 -
                d.post(h);
 
89 -
                return std::noop_coroutine();
 
90 -
            }
 
91 -
            return impl.wait(h, env->executor, std::move(token_), &ec_);
 
92 -
        }
 
93 -
    };
 
94 -

 
95 -
public:
 
96 -
    struct implementation : io_object::implementation
 
97 -
    {
 
98 -
        static constexpr std::size_t npos =
 
99 -
            (std::numeric_limits<std::size_t>::max)();
 
100 -

 
101 -
        std::chrono::steady_clock::time_point expiry_{};
 
102 -
        std::size_t heap_index_ = npos;
 
103 -
        bool might_have_pending_waits_ = false;
 
104 -

 
105 -
        virtual std::coroutine_handle<> wait(
 
106 -
            std::coroutine_handle<>,
 
107 -
            capy::executor_ref,
 
108 -
            std::stop_token,
 
109 -
            std::error_code*) = 0;
 
110 -
    };
 
111 -

 
112  
{
48  
{
113  
public:
49  
public:
114 -
    /// The clock type used for time operations.
50 +
    /// Alias for backward compatibility.
115 -
    using clock_type = std::chrono::steady_clock;
51 +
    using implementation = io_timer::implementation;
116 -

 
117 -
    /// The time point type for absolute expiry times.
 
118 -
    using time_point = clock_type::time_point;
 
119 -

 
120 -
    /// The duration type for relative expiry times.
 
121 -
    using duration = clock_type::duration;
 
122  

52  

123  
    /** Destructor.
53  
    /** Destructor.
124  

54  

125  
        Cancels any pending operations and releases timer resources.
55  
        Cancels any pending operations and releases timer resources.
126  
    */
56  
    */
127  
    ~timer() override;
57  
    ~timer() override;
128  

58  

129  
    /** Construct a timer from an execution context.
59  
    /** Construct a timer from an execution context.
130  

60  

131  
        @param ctx The execution context that will own this timer.
61  
        @param ctx The execution context that will own this timer.
132  
    */
62  
    */
133  
    explicit timer(capy::execution_context& ctx);
63  
    explicit timer(capy::execution_context& ctx);
134  

64  

135  
    /** Construct a timer with an initial absolute expiry time.
65  
    /** Construct a timer with an initial absolute expiry time.
136  

66  

137  
        @param ctx The execution context that will own this timer.
67  
        @param ctx The execution context that will own this timer.
138  
        @param t The initial expiry time point.
68  
        @param t The initial expiry time point.
139  
    */
69  
    */
140  
    timer(capy::execution_context& ctx, time_point t);
70  
    timer(capy::execution_context& ctx, time_point t);
141  

71  

142  
    /** Construct a timer with an initial relative expiry time.
72  
    /** Construct a timer with an initial relative expiry time.
143  

73  

144  
        @param ctx The execution context that will own this timer.
74  
        @param ctx The execution context that will own this timer.
145  
        @param d The initial expiry duration relative to now.
75  
        @param d The initial expiry duration relative to now.
146  
    */
76  
    */
147  
    template<class Rep, class Period>
77  
    template<class Rep, class Period>
148  
    timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
78  
    timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
149  
        : timer(ctx)
79  
        : timer(ctx)
150  
    {
80  
    {
151  
        expires_after(d);
81  
        expires_after(d);
152  
    }
82  
    }
153  

83  

154  
    /** Move constructor.
84  
    /** Move constructor.
155  

85  

156  
        Transfers ownership of the timer resources.
86  
        Transfers ownership of the timer resources.
157  

87  

158  
        @param other The timer to move from.
88  
        @param other The timer to move from.
159  
    */
89  
    */
160  
    timer(timer&& other) noexcept;
90  
    timer(timer&& other) noexcept;
161  

91  

162  
    /** Move assignment operator.
92  
    /** Move assignment operator.
163  

93  

164  
        Closes any existing timer and transfers ownership.
94  
        Closes any existing timer and transfers ownership.
165  

95  

166  
        @param other The timer to move from.
96  
        @param other The timer to move from.
167  

97  

168  
        @return Reference to this timer.
98  
        @return Reference to this timer.
169  
    */
99  
    */
170  
    timer& operator=(timer&& other) noexcept;
100  
    timer& operator=(timer&& other) noexcept;
171  

101  

172 -
    timer(timer const&) = delete;
102 +
    timer(timer const&)            = delete;
173  
    timer& operator=(timer const&) = delete;
103  
    timer& operator=(timer const&) = delete;
174 -
    /** Cancel all pending asynchronous wait operations.
 
175 -

 
176 -
        All outstanding operations complete with an error code that
 
177 -
        compares equal to `capy::cond::canceled`.
 
178 -

 
179 -
        @return The number of operations that were cancelled.
 
180 -
    */
 
181 -
    std::size_t cancel()
 
182 -
    {
 
183 -
        if (!get().might_have_pending_waits_)
 
184 -
            return 0;
 
185 -
        return do_cancel();
 
186 -
    }
 
187 -

 
188  

104  

189  
    /** Cancel one pending asynchronous wait operation.
105  
    /** Cancel one pending asynchronous wait operation.
190  

106  

191  
        The oldest pending wait is cancelled (FIFO order). It
107  
        The oldest pending wait is cancelled (FIFO order). It
192  
        completes with an error code that compares equal to
108  
        completes with an error code that compares equal to
193  
        `capy::cond::canceled`.
109  
        `capy::cond::canceled`.
194  

110  

195  
        @return The number of operations that were cancelled (0 or 1).
111  
        @return The number of operations that were cancelled (0 or 1).
196  
    */
112  
    */
197  
    std::size_t cancel_one()
113  
    std::size_t cancel_one()
198  
    {
114  
    {
199  
        if (!get().might_have_pending_waits_)
115  
        if (!get().might_have_pending_waits_)
200  
            return 0;
116  
            return 0;
201  
        return do_cancel_one();
117  
        return do_cancel_one();
202  
    }
118  
    }
203 -
    /** Return the timer's expiry time as an absolute time.
 
204 -

 
205 -
        @return The expiry time point. If no expiry has been set,
 
206 -
            returns a default-constructed time_point.
 
207 -
    */
 
208 -
    time_point expiry() const noexcept
 
209 -
    {
 
210 -
        return get().expiry_;
 
211 -
    }
 
212 -

 
213  

119  

214  
    /** Set the timer's expiry time as an absolute time.
120  
    /** Set the timer's expiry time as an absolute time.
215  

121  

216  
        Any pending asynchronous wait operations will be cancelled.
122  
        Any pending asynchronous wait operations will be cancelled.
217  

123  

218  
        @param t The expiry time to be used for the timer.
124  
        @param t The expiry time to be used for the timer.
219  

125  

220  
        @return The number of pending operations that were cancelled.
126  
        @return The number of pending operations that were cancelled.
221  
    */
127  
    */
222  
    std::size_t expires_at(time_point t)
128  
    std::size_t expires_at(time_point t)
223  
    {
129  
    {
224 -
        auto& impl = get();
130 +
        auto& impl   = get();
225  
        impl.expiry_ = t;
131  
        impl.expiry_ = t;
226  
        if (impl.heap_index_ == implementation::npos &&
132  
        if (impl.heap_index_ == implementation::npos &&
227  
            !impl.might_have_pending_waits_)
133  
            !impl.might_have_pending_waits_)
228  
            return 0;
134  
            return 0;
229  
        return do_update_expiry();
135  
        return do_update_expiry();
230  
    }
136  
    }
231  

137  

232  
    /** Set the timer's expiry time relative to now.
138  
    /** Set the timer's expiry time relative to now.
233  

139  

234  
        Any pending asynchronous wait operations will be cancelled.
140  
        Any pending asynchronous wait operations will be cancelled.
235  

141  

236  
        @param d The expiry time relative to now.
142  
        @param d The expiry time relative to now.
237  

143  

238  
        @return The number of pending operations that were cancelled.
144  
        @return The number of pending operations that were cancelled.
239  
    */
145  
    */
240  
    std::size_t expires_after(duration d)
146  
    std::size_t expires_after(duration d)
241  
    {
147  
    {
242  
        auto& impl = get();
148  
        auto& impl = get();
243  
        if (d <= duration::zero())
149  
        if (d <= duration::zero())
244  
            impl.expiry_ = (time_point::min)();
150  
            impl.expiry_ = (time_point::min)();
245  
        else
151  
        else
246  
            impl.expiry_ = clock_type::now() + d;
152  
            impl.expiry_ = clock_type::now() + d;
247  
        if (impl.heap_index_ == implementation::npos &&
153  
        if (impl.heap_index_ == implementation::npos &&
248  
            !impl.might_have_pending_waits_)
154  
            !impl.might_have_pending_waits_)
249  
            return 0;
155  
            return 0;
250  
        return do_update_expiry();
156  
        return do_update_expiry();
251  
    }
157  
    }
252  

158  

253  
    /** Set the timer's expiry time relative to now.
159  
    /** Set the timer's expiry time relative to now.
254  

160  

255  
        This is a convenience overload that accepts any duration type
161  
        This is a convenience overload that accepts any duration type
256  
        and converts it to the timer's native duration type. Any
162  
        and converts it to the timer's native duration type. Any
257  
        pending asynchronous wait operations will be cancelled.
163  
        pending asynchronous wait operations will be cancelled.
258  

164  

259  
        @param d The expiry time relative to now.
165  
        @param d The expiry time relative to now.
260  

166  

261  
        @return The number of pending operations that were cancelled.
167  
        @return The number of pending operations that were cancelled.
262  
    */
168  
    */
263  
    template<class Rep, class Period>
169  
    template<class Rep, class Period>
264  
    std::size_t expires_after(std::chrono::duration<Rep, Period> d)
170  
    std::size_t expires_after(std::chrono::duration<Rep, Period> d)
265  
    {
171  
    {
266  
        return expires_after(std::chrono::duration_cast<duration>(d));
172  
        return expires_after(std::chrono::duration_cast<duration>(d));
267  
    }
173  
    }
268  

174  

269 -
    /** Wait for the timer to expire.
175 +
protected:
270 -

176 +
    explicit timer(handle h) noexcept : io_timer(std::move(h)) {}
271 -
        Multiple coroutines may wait on the same timer concurrently.
 
272 -
        When the timer expires, all waiters complete with success.
 
273 -

 
274 -
        The operation supports cancellation via `std::stop_token` through
 
275 -
        the affine awaitable protocol. If the associated stop token is
 
276 -
        triggered, only that waiter completes with an error that
 
277 -
        compares equal to `capy::cond::canceled`; other waiters are
 
278 -
        unaffected.
 
279 -

 
280 -
        @par Example
 
281 -
        @code
 
282 -
        timer t(ctx);
 
283 -
        t.expires_after(std::chrono::seconds(5));
 
284 -
        auto [ec] = co_await t.wait();
 
285 -
        if (ec == capy::cond::canceled)
 
286 -
        {
 
287 -
            // Cancelled via stop_token or cancel()
 
288 -
            co_return;
 
289 -
        }
 
290 -
        if (ec)
 
291 -
        {
 
292 -
            // Handle other errors
 
293 -
            co_return;
 
294 -
        }
 
295 -
        // Timer expired
 
296 -
        @endcode
 
297 -

 
298 -
        @return An awaitable that completes with `io_result<>`.
 
299 -
            Returns success (default error_code) when the timer expires,
 
300 -
            or an error code on failure. Compare against error conditions
 
301 -
            (e.g., `ec == capy::cond::canceled`) rather than error codes.
 
302 -

 
303 -
        @par Preconditions
 
304 -
        The timer must have an expiry time set via expires_at() or
 
305 -
        expires_after().
 
306 -
    */
 
307 -
    auto wait()
 
308 -
    {
 
309 -
        return wait_awaitable(*this);
 
310 -
    }
 
311  

177  

312  
private:
178  
private:
313 -
    // Out-of-line cancel/expiry when inline fast-path
179 +
    std::size_t do_cancel() override;
314 -
    // conditions (no waiters, not in heap) are not met.
 
315 -
    std::size_t do_cancel();
 
316  
    std::size_t do_cancel_one();
180  
    std::size_t do_cancel_one();
317  
    std::size_t do_update_expiry();
181  
    std::size_t do_update_expiry();
318  

182  

319  
    /// Return the underlying implementation.
183  
    /// Return the underlying implementation.
320  
    implementation& get() const noexcept
184  
    implementation& get() const noexcept
321  
    {
185  
    {
322  
        return *static_cast<implementation*>(h_.get());
186  
        return *static_cast<implementation*>(h_.get());
323  
    }
187  
    }
324  
};
188  
};
325  

189  

326  
} // namespace boost::corosio
190  
} // namespace boost::corosio
327  

191  

328  
#endif
192  
#endif