1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
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/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_TIMER_HPP
10  
#ifndef BOOST_COROSIO_TIMER_HPP
11  
#define BOOST_COROSIO_TIMER_HPP
11  
#define BOOST_COROSIO_TIMER_HPP
12  

12  

13  
#include <boost/corosio/detail/config.hpp>
13  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/except.hpp>
14  
#include <boost/corosio/detail/except.hpp>
15  
#include <boost/corosio/io_object.hpp>
15  
#include <boost/corosio/io_object.hpp>
16  
#include <boost/capy/io_result.hpp>
16  
#include <boost/capy/io_result.hpp>
17  
#include <boost/capy/error.hpp>
17  
#include <boost/capy/error.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/execution_context.hpp>
20  
#include <boost/capy/concept/executor.hpp>
20  
#include <boost/capy/concept/executor.hpp>
21  
#include <system_error>
21  
#include <system_error>
22  

22  

23  
#include <chrono>
23  
#include <chrono>
24  
#include <concepts>
24  
#include <concepts>
25  
#include <coroutine>
25  
#include <coroutine>
26  
#include <stop_token>
26  
#include <stop_token>
27  
#include <type_traits>
27  
#include <type_traits>
28  

28  

29  
namespace boost::corosio {
29  
namespace boost::corosio {
30  

30  

31  
/** An asynchronous timer for coroutine I/O.
31  
/** An asynchronous timer for coroutine I/O.
32  

32  

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

36  

37  
    Each timer operation participates in the affine awaitable protocol,
37  
    Each timer operation participates in the affine awaitable protocol,
38  
    ensuring coroutines resume on the correct executor.
38  
    ensuring coroutines resume on the correct executor.
39  

39  

40  
    @par Thread Safety
40  
    @par Thread Safety
41  
    Distinct objects: Safe.@n
41  
    Distinct objects: Safe.@n
42  
    Shared objects: Unsafe. A timer must not have concurrent wait
42  
    Shared objects: Unsafe. A timer must not have concurrent wait
43  
    operations.
43  
    operations.
44  

44  

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

57  

58  
        explicit wait_awaitable(timer& t) noexcept : t_(t) {}
58  
        explicit wait_awaitable(timer& t) noexcept : t_(t) {}
59  

59  

60  
        bool await_ready() const noexcept
60  
        bool await_ready() const noexcept
61  
        {
61  
        {
62  
            return token_.stop_requested();
62  
            return token_.stop_requested();
63  
        }
63  
        }
64  

64  

65  
        capy::io_result<> await_resume() const noexcept
65  
        capy::io_result<> await_resume() const noexcept
66  
        {
66  
        {
67  
            if (token_.stop_requested())
67  
            if (token_.stop_requested())
68  
                return {capy::error::canceled};
68  
                return {capy::error::canceled};
69  
            return {ec_};
69  
            return {ec_};
70  
        }
70  
        }
71  

71  

72  
        template<typename Ex>
72  
        template<typename Ex>
73  
        auto await_suspend(
73  
        auto await_suspend(
74  
            std::coroutine_handle<> h,
74  
            std::coroutine_handle<> h,
75  
            Ex const& ex) -> std::coroutine_handle<>
75  
            Ex const& ex) -> std::coroutine_handle<>
76  
        {
76  
        {
77  
            t_.get().wait(h, ex, token_, &ec_);
77  
            t_.get().wait(h, ex, token_, &ec_);
78  
            return std::noop_coroutine();
78  
            return std::noop_coroutine();
79  
        }
79  
        }
80  

80  

81  
        template<typename Ex>
81  
        template<typename Ex>
82  
        auto await_suspend(
82  
        auto await_suspend(
83  
            std::coroutine_handle<> h,
83  
            std::coroutine_handle<> h,
84  
            Ex const& ex,
84  
            Ex const& ex,
85  
            std::stop_token token) -> std::coroutine_handle<>
85  
            std::stop_token token) -> std::coroutine_handle<>
86  
        {
86  
        {
87  
            token_ = std::move(token);
87  
            token_ = std::move(token);
88  
            t_.get().wait(h, ex, token_, &ec_);
88  
            t_.get().wait(h, ex, token_, &ec_);
89  
            return std::noop_coroutine();
89  
            return std::noop_coroutine();
90  
        }
90  
        }
91  
    };
91  
    };
92  

92  

93  
public:
93  
public:
94  
    struct timer_impl : io_object_impl
94  
    struct timer_impl : io_object_impl
95  
    {
95  
    {
96  
        virtual void wait(
96  
        virtual void wait(
97  
            std::coroutine_handle<>,
97  
            std::coroutine_handle<>,
98  
            capy::executor_ref,
98  
            capy::executor_ref,
99  
            std::stop_token,
99  
            std::stop_token,
100  
            std::error_code*) = 0;
100  
            std::error_code*) = 0;
101  
    };
101  
    };
102  

102  

103  
public:
103  
public:
104  
    /// The clock type used for time operations.
104  
    /// The clock type used for time operations.
105  
    using clock_type = std::chrono::steady_clock;
105  
    using clock_type = std::chrono::steady_clock;
106  

106  

107  
    /// The time point type for absolute expiry times.
107  
    /// The time point type for absolute expiry times.
108  
    using time_point = clock_type::time_point;
108  
    using time_point = clock_type::time_point;
109  

109  

110  
    /// The duration type for relative expiry times.
110  
    /// The duration type for relative expiry times.
111  
    using duration = clock_type::duration;
111  
    using duration = clock_type::duration;
112  

112  

113  
    /** Destructor.
113  
    /** Destructor.
114  

114  

115  
        Cancels any pending operations and releases timer resources.
115  
        Cancels any pending operations and releases timer resources.
116  
    */
116  
    */
117  
    ~timer();
117  
    ~timer();
118  

118  

119  
    /** Construct a timer from an execution context.
119  
    /** Construct a timer from an execution context.
120  

120  

121  
        @param ctx The execution context that will own this timer.
121  
        @param ctx The execution context that will own this timer.
122  
    */
122  
    */
123  
    explicit timer(capy::execution_context& ctx);
123  
    explicit timer(capy::execution_context& ctx);
124  

124  

125  
    /** Move constructor.
125  
    /** Move constructor.
126  

126  

127  
        Transfers ownership of the timer resources.
127  
        Transfers ownership of the timer resources.
128  

128  

129  
        @param other The timer to move from.
129  
        @param other The timer to move from.
130  
    */
130  
    */
131  
    timer(timer&& other) noexcept;
131  
    timer(timer&& other) noexcept;
132  

132  

133  
    /** Move assignment operator.
133  
    /** Move assignment operator.
134  

134  

135  
        Closes any existing timer and transfers ownership.
135  
        Closes any existing timer and transfers ownership.
136  
        The source and destination must share the same execution context.
136  
        The source and destination must share the same execution context.
137  

137  

138  
        @param other The timer to move from.
138  
        @param other The timer to move from.
139  

139  

140  
        @return Reference to this timer.
140  
        @return Reference to this timer.
141  

141  

142  
        @throws std::logic_error if the timers have different execution contexts.
142  
        @throws std::logic_error if the timers have different execution contexts.
143  
    */
143  
    */
144  
    timer& operator=(timer&& other);
144  
    timer& operator=(timer&& other);
145  

145  

146  
    timer(timer const&) = delete;
146  
    timer(timer const&) = delete;
147  
    timer& operator=(timer const&) = delete;
147  
    timer& operator=(timer const&) = delete;
148  

148  

149  
    /** Cancel any pending asynchronous operations.
149  
    /** Cancel any pending asynchronous operations.
150  

150  

151  
        All outstanding operations complete with an error code that
151  
        All outstanding operations complete with an error code that
152  
        compares equal to `capy::cond::canceled`.
152  
        compares equal to `capy::cond::canceled`.
153  
    */
153  
    */
154  
    void cancel();
154  
    void cancel();
155  

155  

156  
    /** Get the timer's expiry time as an absolute time.
156  
    /** Get the timer's expiry time as an absolute time.
157  

157  

158  
        @return The expiry time point. If no expiry has been set,
158  
        @return The expiry time point. If no expiry has been set,
159  
            returns a default-constructed time_point.
159  
            returns a default-constructed time_point.
160  
    */
160  
    */
161  
    time_point expiry() const;
161  
    time_point expiry() const;
162  

162  

163  
    /** Set the timer's expiry time as an absolute time.
163  
    /** Set the timer's expiry time as an absolute time.
164  

164  

165  
        Any pending asynchronous wait operations will be cancelled.
165  
        Any pending asynchronous wait operations will be cancelled.
166  

166  

167  
        @param t The expiry time to be used for the timer.
167  
        @param t The expiry time to be used for the timer.
168  
    */
168  
    */
169  
    void expires_at(time_point t);
169  
    void expires_at(time_point t);
170  

170  

171  
    /** Set the timer's expiry time relative to now.
171  
    /** Set the timer's expiry time relative to now.
172  

172  

173  
        Any pending asynchronous wait operations will be cancelled.
173  
        Any pending asynchronous wait operations will be cancelled.
174  

174  

175  
        @param d The expiry time relative to now.
175  
        @param d The expiry time relative to now.
176  
    */
176  
    */
177  
    void expires_after(duration d);
177  
    void expires_after(duration d);
178  

178  

179  
    /** Set the timer's expiry time relative to now.
179  
    /** Set the timer's expiry time relative to now.
180  

180  

181  
        This is a convenience overload that accepts any duration type
181  
        This is a convenience overload that accepts any duration type
182  
        and converts it to the timer's native duration type.
182  
        and converts it to the timer's native duration type.
183  

183  

184  
        @param d The expiry time relative to now.
184  
        @param d The expiry time relative to now.
185  
    */
185  
    */
186  
    template<class Rep, class Period>
186  
    template<class Rep, class Period>
187  
    void expires_after(std::chrono::duration<Rep, Period> d)
187  
    void expires_after(std::chrono::duration<Rep, Period> d)
188  
    {
188  
    {
189  
        expires_after(std::chrono::duration_cast<duration>(d));
189  
        expires_after(std::chrono::duration_cast<duration>(d));
190  
    }
190  
    }
191  

191  

192  
    /** Wait for the timer to expire.
192  
    /** Wait for the timer to expire.
193  

193  

194  
        The operation supports cancellation via `std::stop_token` through
194  
        The operation supports cancellation via `std::stop_token` through
195  
        the affine awaitable protocol. If the associated stop token is
195  
        the affine awaitable protocol. If the associated stop token is
196  
        triggered, the operation completes immediately with an error
196  
        triggered, the operation completes immediately with an error
197  
        that compares equal to `capy::cond::canceled`.
197  
        that compares equal to `capy::cond::canceled`.
198  

198  

199  
        @par Example
199  
        @par Example
200  
        @code
200  
        @code
201  
        timer t(ctx);
201  
        timer t(ctx);
202  
        t.expires_after(std::chrono::seconds(5));
202  
        t.expires_after(std::chrono::seconds(5));
203  
        auto [ec] = co_await t.wait();
203  
        auto [ec] = co_await t.wait();
204  
        if (ec == capy::cond::canceled)
204  
        if (ec == capy::cond::canceled)
205  
        {
205  
        {
206  
            // Cancelled via stop_token or cancel()
206  
            // Cancelled via stop_token or cancel()
207  
            co_return;
207  
            co_return;
208  
        }
208  
        }
209  
        if (ec)
209  
        if (ec)
210  
        {
210  
        {
211  
            // Handle other errors
211  
            // Handle other errors
212  
            co_return;
212  
            co_return;
213  
        }
213  
        }
214  
        // Timer expired
214  
        // Timer expired
215  
        @endcode
215  
        @endcode
216  

216  

217  
        @return An awaitable that completes with `io_result<>`.
217  
        @return An awaitable that completes with `io_result<>`.
218  
            Returns success (default error_code) when the timer expires,
218  
            Returns success (default error_code) when the timer expires,
219  
            or an error code on failure. Compare against error conditions
219  
            or an error code on failure. Compare against error conditions
220  
            (e.g., `ec == capy::cond::canceled`) rather than error codes.
220  
            (e.g., `ec == capy::cond::canceled`) rather than error codes.
221  

221  

222  
        @par Preconditions
222  
        @par Preconditions
223  
        The timer must have an expiry time set via expires_at() or
223  
        The timer must have an expiry time set via expires_at() or
224  
        expires_after().
224  
        expires_after().
225  
    */
225  
    */
226  
    auto wait()
226  
    auto wait()
227  
    {
227  
    {
228  
        return wait_awaitable(*this);
228  
        return wait_awaitable(*this);
229  
    }
229  
    }
230  

230  

231  
private:
231  
private:
232  
    timer_impl& get() const noexcept
232  
    timer_impl& get() const noexcept
233  
    {
233  
    {
234  
        return *static_cast<timer_impl*>(impl_);
234  
        return *static_cast<timer_impl*>(impl_);
235  
    }
235  
    }
236  
};
236  
};
237  

237  

238  
} // namespace boost::corosio
238  
} // namespace boost::corosio
239  

239  

240  
#endif
240  
#endif