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  
            return t_.get().wait(h, ex, token_, &ec_);
77  
            return t_.get().wait(h, ex, token_, &ec_);
78  
        }
78  
        }
79  

79  

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

90  

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

100  

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

104  

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

107  

108  
    /// The duration type for relative expiry times.
108  
    /// The duration type for relative expiry times.
109  
    using duration = clock_type::duration;
109  
    using duration = clock_type::duration;
110  

110  

111  
    /** Destructor.
111  
    /** Destructor.
112  

112  

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

116  

117  
    /** Construct a timer from an execution context.
117  
    /** Construct a timer from an execution context.
118  

118  

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

122  

123  
    /** Move constructor.
123  
    /** Move constructor.
124  

124  

125  
        Transfers ownership of the timer resources.
125  
        Transfers ownership of the timer resources.
126  

126  

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

130  

131  
    /** Move assignment operator.
131  
    /** Move assignment operator.
132  

132  

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

135  

136  
        @param other The timer to move from.
136  
        @param other The timer to move from.
137  

137  

138  
        @return Reference to this timer.
138  
        @return Reference to this timer.
139  

139  

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

143  

144  
    timer(timer const&) = delete;
144  
    timer(timer const&) = delete;
145  
    timer& operator=(timer const&) = delete;
145  
    timer& operator=(timer const&) = delete;
146  

146  

147  
    /** Cancel any pending asynchronous operations.
147  
    /** Cancel any pending asynchronous operations.
148  

148  

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

153  

154  
    /** Get the timer's expiry time as an absolute time.
154  
    /** Get the timer's expiry time as an absolute time.
155  

155  

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

160  

161  
    /** Set the timer's expiry time as an absolute time.
161  
    /** Set the timer's expiry time as an absolute time.
162  

162  

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

164  

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

168  

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

170  

171  
        Any pending asynchronous wait operations will be cancelled.
171  
        Any pending asynchronous wait operations will be cancelled.
172  

172  

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

176  

177  
    /** Set the timer's expiry time relative to now.
177  
    /** Set the timer's expiry time relative to now.
178  

178  

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

181  

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

189  

190  
    /** Wait for the timer to expire.
190  
    /** Wait for the timer to expire.
191  

191  

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

196  

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

214  

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

219  

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

228  

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

235  

236  
} // namespace boost::corosio
236  
} // namespace boost::corosio
237  

237  

238  
#endif
238  
#endif