1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/concept/executor.hpp>
21  
#include <boost/capy/concept/executor.hpp>
22  
#include <system_error>
22  
#include <system_error>
23  

23  

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

29  

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

31  

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

33  

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

37  

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

40  

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

45  

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

58  

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

60  

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

65  

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

72  

73  
        auto await_suspend(
73  
        auto await_suspend(
74  
            std::coroutine_handle<> h,
74  
            std::coroutine_handle<> h,
75  
            capy::io_env const* env) -> std::coroutine_handle<>
75  
            capy::io_env const* env) -> std::coroutine_handle<>
76  
        {
76  
        {
77  
            token_ = env->stop_token;
77  
            token_ = env->stop_token;
78  
            return t_.get().wait(h, env->executor, token_, &ec_);
78  
            return t_.get().wait(h, env->executor, token_, &ec_);
79  
        }
79  
        }
80  
    };
80  
    };
81  

81  

82  
public:
82  
public:
83  
    struct timer_impl : io_object_impl
83  
    struct timer_impl : io_object_impl
84  
    {
84  
    {
85  
        virtual std::coroutine_handle<> wait(
85  
        virtual std::coroutine_handle<> wait(
86  
            std::coroutine_handle<>,
86  
            std::coroutine_handle<>,
87  
            capy::executor_ref,
87  
            capy::executor_ref,
88  
            std::stop_token,
88  
            std::stop_token,
89  
            std::error_code*) = 0;
89  
            std::error_code*) = 0;
90  
    };
90  
    };
91  

91  

92  
public:
92  
public:
93  
    /// The clock type used for time operations.
93  
    /// The clock type used for time operations.
94  
    using clock_type = std::chrono::steady_clock;
94  
    using clock_type = std::chrono::steady_clock;
95  

95  

96  
    /// The time point type for absolute expiry times.
96  
    /// The time point type for absolute expiry times.
97  
    using time_point = clock_type::time_point;
97  
    using time_point = clock_type::time_point;
98  

98  

99  
    /// The duration type for relative expiry times.
99  
    /// The duration type for relative expiry times.
100  
    using duration = clock_type::duration;
100  
    using duration = clock_type::duration;
101  

101  

102  
    /** Destructor.
102  
    /** Destructor.
103  

103  

104  
        Cancels any pending operations and releases timer resources.
104  
        Cancels any pending operations and releases timer resources.
105  
    */
105  
    */
106  
    ~timer();
106  
    ~timer();
107  

107  

108  
    /** Construct a timer from an execution context.
108  
    /** Construct a timer from an execution context.
109  

109  

110  
        @param ctx The execution context that will own this timer.
110  
        @param ctx The execution context that will own this timer.
111  
    */
111  
    */
112  
    explicit timer(capy::execution_context& ctx);
112  
    explicit timer(capy::execution_context& ctx);
113  

113  

114  
    /** Move constructor.
114  
    /** Move constructor.
115  

115  

116  
        Transfers ownership of the timer resources.
116  
        Transfers ownership of the timer resources.
117  

117  

118  
        @param other The timer to move from.
118  
        @param other The timer to move from.
119  
    */
119  
    */
120  
    timer(timer&& other) noexcept;
120  
    timer(timer&& other) noexcept;
121  

121  

122  
    /** Move assignment operator.
122  
    /** Move assignment operator.
123  

123  

124  
        Closes any existing timer and transfers ownership.
124  
        Closes any existing timer and transfers ownership.
125  
        The source and destination must share the same execution context.
125  
        The source and destination must share the same execution context.
126  

126  

127  
        @param other The timer to move from.
127  
        @param other The timer to move from.
128  

128  

129  
        @return Reference to this timer.
129  
        @return Reference to this timer.
130  

130  

131  
        @throws std::logic_error if the timers have different execution contexts.
131  
        @throws std::logic_error if the timers have different execution contexts.
132  
    */
132  
    */
133  
    timer& operator=(timer&& other);
133  
    timer& operator=(timer&& other);
134  

134  

135  
    timer(timer const&) = delete;
135  
    timer(timer const&) = delete;
136  
    timer& operator=(timer const&) = delete;
136  
    timer& operator=(timer const&) = delete;
137  

137  

138  
    /** Cancel any pending asynchronous operations.
138  
    /** Cancel any pending asynchronous operations.
139  

139  

140  
        All outstanding operations complete with an error code that
140  
        All outstanding operations complete with an error code that
141  
        compares equal to `capy::cond::canceled`.
141  
        compares equal to `capy::cond::canceled`.
142  
    */
142  
    */
143  
    void cancel();
143  
    void cancel();
144  

144  

145  
    /** Get the timer's expiry time as an absolute time.
145  
    /** Get the timer's expiry time as an absolute time.
146  

146  

147  
        @return The expiry time point. If no expiry has been set,
147  
        @return The expiry time point. If no expiry has been set,
148  
            returns a default-constructed time_point.
148  
            returns a default-constructed time_point.
149  
    */
149  
    */
150  
    time_point expiry() const;
150  
    time_point expiry() const;
151  

151  

152  
    /** Set the timer's expiry time as an absolute time.
152  
    /** Set the timer's expiry time as an absolute time.
153  

153  

154  
        Any pending asynchronous wait operations will be cancelled.
154  
        Any pending asynchronous wait operations will be cancelled.
155  

155  

156  
        @param t The expiry time to be used for the timer.
156  
        @param t The expiry time to be used for the timer.
157  
    */
157  
    */
158  
    void expires_at(time_point t);
158  
    void expires_at(time_point t);
159  

159  

160  
    /** Set the timer's expiry time relative to now.
160  
    /** Set the timer's expiry time relative to now.
161  

161  

162  
        Any pending asynchronous wait operations will be cancelled.
162  
        Any pending asynchronous wait operations will be cancelled.
163  

163  

164  
        @param d The expiry time relative to now.
164  
        @param d The expiry time relative to now.
165  
    */
165  
    */
166  
    void expires_after(duration d);
166  
    void expires_after(duration d);
167  

167  

168  
    /** Set the timer's expiry time relative to now.
168  
    /** Set the timer's expiry time relative to now.
169  

169  

170  
        This is a convenience overload that accepts any duration type
170  
        This is a convenience overload that accepts any duration type
171  
        and converts it to the timer's native duration type.
171  
        and converts it to the timer's native duration type.
172  

172  

173  
        @param d The expiry time relative to now.
173  
        @param d The expiry time relative to now.
174  
    */
174  
    */
175  
    template<class Rep, class Period>
175  
    template<class Rep, class Period>
176  
    void expires_after(std::chrono::duration<Rep, Period> d)
176  
    void expires_after(std::chrono::duration<Rep, Period> d)
177  
    {
177  
    {
178  
        expires_after(std::chrono::duration_cast<duration>(d));
178  
        expires_after(std::chrono::duration_cast<duration>(d));
179  
    }
179  
    }
180  

180  

181  
    /** Wait for the timer to expire.
181  
    /** Wait for the timer to expire.
182  

182  

183  
        The operation supports cancellation via `std::stop_token` through
183  
        The operation supports cancellation via `std::stop_token` through
184  
        the affine awaitable protocol. If the associated stop token is
184  
        the affine awaitable protocol. If the associated stop token is
185  
        triggered, the operation completes immediately with an error
185  
        triggered, the operation completes immediately with an error
186  
        that compares equal to `capy::cond::canceled`.
186  
        that compares equal to `capy::cond::canceled`.
187  

187  

188  
        @par Example
188  
        @par Example
189  
        @code
189  
        @code
190  
        timer t(ctx);
190  
        timer t(ctx);
191  
        t.expires_after(std::chrono::seconds(5));
191  
        t.expires_after(std::chrono::seconds(5));
192  
        auto [ec] = co_await t.wait();
192  
        auto [ec] = co_await t.wait();
193  
        if (ec == capy::cond::canceled)
193  
        if (ec == capy::cond::canceled)
194  
        {
194  
        {
195  
            // Cancelled via stop_token or cancel()
195  
            // Cancelled via stop_token or cancel()
196  
            co_return;
196  
            co_return;
197  
        }
197  
        }
198  
        if (ec)
198  
        if (ec)
199  
        {
199  
        {
200  
            // Handle other errors
200  
            // Handle other errors
201  
            co_return;
201  
            co_return;
202  
        }
202  
        }
203  
        // Timer expired
203  
        // Timer expired
204  
        @endcode
204  
        @endcode
205  

205  

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

210  

211  
        @par Preconditions
211  
        @par Preconditions
212  
        The timer must have an expiry time set via expires_at() or
212  
        The timer must have an expiry time set via expires_at() or
213  
        expires_after().
213  
        expires_after().
214  
    */
214  
    */
215  
    auto wait()
215  
    auto wait()
216  
    {
216  
    {
217  
        return wait_awaitable(*this);
217  
        return wait_awaitable(*this);
218  
    }
218  
    }
219  

219  

220  
private:
220  
private:
221  
    timer_impl& get() const noexcept
221  
    timer_impl& get() const noexcept
222  
    {
222  
    {
223  
        return *static_cast<timer_impl*>(impl_);
223  
        return *static_cast<timer_impl*>(impl_);
224  
    }
224  
    }
225  
};
225  
};
226  

226  

227  
} // namespace boost::corosio
227  
} // namespace boost::corosio
228  

228  

229  
#endif
229  
#endif