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/detail/except.hpp>
15  
#include <boost/corosio/detail/except.hpp>
16  
#include <boost/corosio/io_object.hpp>
16  
#include <boost/corosio/io_object.hpp>
17  
#include <boost/capy/io_result.hpp>
17  
#include <boost/capy/io_result.hpp>
18  
#include <boost/capy/error.hpp>
18  
#include <boost/capy/error.hpp>
19  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <boost/capy/ex/executor_ref.hpp>
20  
#include <boost/capy/ex/execution_context.hpp>
20  
#include <boost/capy/ex/execution_context.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
22  
#include <boost/capy/concept/executor.hpp>
22  
#include <boost/capy/concept/executor.hpp>
23  
#include <system_error>
23  
#include <system_error>
24  

24  

25  
#include <chrono>
25  
#include <chrono>
26  
#include <coroutine>
26  
#include <coroutine>
27  
#include <cstddef>
27  
#include <cstddef>
28  
#include <stop_token>
28  
#include <stop_token>
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  
    Multiple coroutines may wait concurrently on the same timer.
38  
    Multiple coroutines may wait concurrently on the same timer.
39  
    When the timer expires, all waiters complete with success. When
39  
    When the timer expires, all waiters complete with success. When
40  
    the timer is cancelled, all waiters complete with an error that
40  
    the timer is cancelled, all waiters complete with an error that
41  
    compares equal to `capy::cond::canceled`.
41  
    compares equal to `capy::cond::canceled`.
42  

42  

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

45  

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

49  

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

62  

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

64  

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

69  

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

76  

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

85  

86  
public:
86  
public:
87  
    struct timer_impl : io_object_impl
87  
    struct timer_impl : io_object_impl
88  
    {
88  
    {
89  
        virtual std::coroutine_handle<> wait(
89  
        virtual std::coroutine_handle<> wait(
90  
            std::coroutine_handle<>,
90  
            std::coroutine_handle<>,
91  
            capy::executor_ref,
91  
            capy::executor_ref,
92  
            std::stop_token,
92  
            std::stop_token,
93  
            std::error_code*) = 0;
93  
            std::error_code*) = 0;
94  
    };
94  
    };
95  

95  

96  
public:
96  
public:
97  
    /// The clock type used for time operations.
97  
    /// The clock type used for time operations.
98  
    using clock_type = std::chrono::steady_clock;
98  
    using clock_type = std::chrono::steady_clock;
99  

99  

100  
    /// The time point type for absolute expiry times.
100  
    /// The time point type for absolute expiry times.
101  
    using time_point = clock_type::time_point;
101  
    using time_point = clock_type::time_point;
102  

102  

103  
    /// The duration type for relative expiry times.
103  
    /// The duration type for relative expiry times.
104  
    using duration = clock_type::duration;
104  
    using duration = clock_type::duration;
105  

105  

106  
    /** Destructor.
106  
    /** Destructor.
107  

107  

108  
        Cancels any pending operations and releases timer resources.
108  
        Cancels any pending operations and releases timer resources.
109  
    */
109  
    */
110  
    ~timer();
110  
    ~timer();
111  

111  

112  
    /** Construct a timer from an execution context.
112  
    /** Construct a timer from an execution context.
113  

113  

114  
        @param ctx The execution context that will own this timer.
114  
        @param ctx The execution context that will own this timer.
115  
    */
115  
    */
116  
    explicit timer(capy::execution_context& ctx);
116  
    explicit timer(capy::execution_context& ctx);
117  

117  

118  
    /** Construct a timer with an initial absolute expiry time.
118  
    /** Construct a timer with an initial absolute expiry time.
119  

119  

120  
        @param ctx The execution context that will own this timer.
120  
        @param ctx The execution context that will own this timer.
121  
        @param t The initial expiry time point.
121  
        @param t The initial expiry time point.
122  
    */
122  
    */
123  
    timer(capy::execution_context& ctx, time_point t);
123  
    timer(capy::execution_context& ctx, time_point t);
124  

124  

125  
    /** Construct a timer with an initial relative expiry time.
125  
    /** Construct a timer with an initial relative expiry time.
126  

126  

127  
        @param ctx The execution context that will own this timer.
127  
        @param ctx The execution context that will own this timer.
128  
        @param d The initial expiry duration relative to now.
128  
        @param d The initial expiry duration relative to now.
129  
    */
129  
    */
130  
    template<class Rep, class Period>
130  
    template<class Rep, class Period>
131  
    timer(
131  
    timer(
132  
        capy::execution_context& ctx,
132  
        capy::execution_context& ctx,
133  
        std::chrono::duration<Rep, Period> d)
133  
        std::chrono::duration<Rep, Period> d)
134  
        : timer(ctx)
134  
        : timer(ctx)
135  
    {
135  
    {
136  
        expires_after(d);
136  
        expires_after(d);
137  
    }
137  
    }
138  

138  

139  
    /** Move constructor.
139  
    /** Move constructor.
140  

140  

141  
        Transfers ownership of the timer resources.
141  
        Transfers ownership of the timer resources.
142  

142  

143  
        @param other The timer to move from.
143  
        @param other The timer to move from.
144  
    */
144  
    */
145  
    timer(timer&& other) noexcept;
145  
    timer(timer&& other) noexcept;
146  

146  

147  
    /** Move assignment operator.
147  
    /** Move assignment operator.
148  

148  

149  
        Closes any existing timer and transfers ownership.
149  
        Closes any existing timer and transfers ownership.
150  
        The source and destination must share the same execution context.
150  
        The source and destination must share the same execution context.
151  

151  

152  
        @param other The timer to move from.
152  
        @param other The timer to move from.
153  

153  

154  
        @return Reference to this timer.
154  
        @return Reference to this timer.
155  

155  

156  
        @throws std::logic_error if the timers have different execution contexts.
156  
        @throws std::logic_error if the timers have different execution contexts.
157  
    */
157  
    */
158  
    timer& operator=(timer&& other);
158  
    timer& operator=(timer&& other);
159  

159  

160  
    timer(timer const&) = delete;
160  
    timer(timer const&) = delete;
161  
    timer& operator=(timer const&) = delete;
161  
    timer& operator=(timer const&) = delete;
162  

162  

163  
    /** Cancel all pending asynchronous wait operations.
163  
    /** Cancel all pending asynchronous wait operations.
164  

164  

165  
        All outstanding operations complete with an error code that
165  
        All outstanding operations complete with an error code that
166  
        compares equal to `capy::cond::canceled`.
166  
        compares equal to `capy::cond::canceled`.
167  

167  

168  
        @return The number of operations that were cancelled.
168  
        @return The number of operations that were cancelled.
169  
    */
169  
    */
170  
    std::size_t cancel();
170  
    std::size_t cancel();
171  

171  

172  
    /** Cancel one pending asynchronous wait operation.
172  
    /** Cancel one pending asynchronous wait operation.
173  

173  

174  
        The oldest pending wait is cancelled (FIFO order). It
174  
        The oldest pending wait is cancelled (FIFO order). It
175  
        completes with an error code that compares equal to
175  
        completes with an error code that compares equal to
176  
        `capy::cond::canceled`.
176  
        `capy::cond::canceled`.
177  

177  

178  
        @return The number of operations that were cancelled (0 or 1).
178  
        @return The number of operations that were cancelled (0 or 1).
179  
    */
179  
    */
180  
    std::size_t cancel_one();
180  
    std::size_t cancel_one();
181  

181  

182  
    /** Return the timer's expiry time as an absolute time.
182  
    /** Return the timer's expiry time as an absolute time.
183  

183  

184  
        @return The expiry time point. If no expiry has been set,
184  
        @return The expiry time point. If no expiry has been set,
185  
            returns a default-constructed time_point.
185  
            returns a default-constructed time_point.
186  
    */
186  
    */
187  
    time_point expiry() const;
187  
    time_point expiry() const;
188  

188  

189  
    /** Set the timer's expiry time as an absolute time.
189  
    /** Set the timer's expiry time as an absolute time.
190  

190  

191  
        Any pending asynchronous wait operations will be cancelled.
191  
        Any pending asynchronous wait operations will be cancelled.
192  

192  

193  
        @param t The expiry time to be used for the timer.
193  
        @param t The expiry time to be used for the timer.
194  

194  

195  
        @return The number of pending operations that were cancelled.
195  
        @return The number of pending operations that were cancelled.
196  
    */
196  
    */
197  
    std::size_t expires_at(time_point t);
197  
    std::size_t expires_at(time_point t);
198  

198  

199  
    /** Set the timer's expiry time relative to now.
199  
    /** Set the timer's expiry time relative to now.
200  

200  

201  
        Any pending asynchronous wait operations will be cancelled.
201  
        Any pending asynchronous wait operations will be cancelled.
202  

202  

203  
        @param d The expiry time relative to now.
203  
        @param d The expiry time relative to now.
204  

204  

205  
        @return The number of pending operations that were cancelled.
205  
        @return The number of pending operations that were cancelled.
206  
    */
206  
    */
207  
    std::size_t expires_after(duration d);
207  
    std::size_t expires_after(duration d);
208  

208  

209  
    /** Set the timer's expiry time relative to now.
209  
    /** Set the timer's expiry time relative to now.
210  

210  

211  
        This is a convenience overload that accepts any duration type
211  
        This is a convenience overload that accepts any duration type
212  
        and converts it to the timer's native duration type. Any
212  
        and converts it to the timer's native duration type. Any
213  
        pending asynchronous wait operations will be cancelled.
213  
        pending asynchronous wait operations will be cancelled.
214  

214  

215  
        @param d The expiry time relative to now.
215  
        @param d The expiry time relative to now.
216  

216  

217  
        @return The number of pending operations that were cancelled.
217  
        @return The number of pending operations that were cancelled.
218  
    */
218  
    */
219  
    template<class Rep, class Period>
219  
    template<class Rep, class Period>
220  
    std::size_t expires_after(std::chrono::duration<Rep, Period> d)
220  
    std::size_t expires_after(std::chrono::duration<Rep, Period> d)
221  
    {
221  
    {
222  
        return expires_after(std::chrono::duration_cast<duration>(d));
222  
        return expires_after(std::chrono::duration_cast<duration>(d));
223  
    }
223  
    }
224  

224  

225  
    /** Wait for the timer to expire.
225  
    /** Wait for the timer to expire.
226  

226  

227  
        Multiple coroutines may wait on the same timer concurrently.
227  
        Multiple coroutines may wait on the same timer concurrently.
228  
        When the timer expires, all waiters complete with success.
228  
        When the timer expires, all waiters complete with success.
229  

229  

230  
        The operation supports cancellation via `std::stop_token` through
230  
        The operation supports cancellation via `std::stop_token` through
231  
        the affine awaitable protocol. If the associated stop token is
231  
        the affine awaitable protocol. If the associated stop token is
232  
        triggered, only that waiter completes with an error that
232  
        triggered, only that waiter completes with an error that
233  
        compares equal to `capy::cond::canceled`; other waiters are
233  
        compares equal to `capy::cond::canceled`; other waiters are
234  
        unaffected.
234  
        unaffected.
235  

235  

236  
        @par Example
236  
        @par Example
237  
        @code
237  
        @code
238  
        timer t(ctx);
238  
        timer t(ctx);
239  
        t.expires_after(std::chrono::seconds(5));
239  
        t.expires_after(std::chrono::seconds(5));
240  
        auto [ec] = co_await t.wait();
240  
        auto [ec] = co_await t.wait();
241  
        if (ec == capy::cond::canceled)
241  
        if (ec == capy::cond::canceled)
242  
        {
242  
        {
243  
            // Cancelled via stop_token or cancel()
243  
            // Cancelled via stop_token or cancel()
244  
            co_return;
244  
            co_return;
245  
        }
245  
        }
246  
        if (ec)
246  
        if (ec)
247  
        {
247  
        {
248  
            // Handle other errors
248  
            // Handle other errors
249  
            co_return;
249  
            co_return;
250  
        }
250  
        }
251  
        // Timer expired
251  
        // Timer expired
252  
        @endcode
252  
        @endcode
253  

253  

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

258  

259  
        @par Preconditions
259  
        @par Preconditions
260  
        The timer must have an expiry time set via expires_at() or
260  
        The timer must have an expiry time set via expires_at() or
261  
        expires_after().
261  
        expires_after().
262  
    */
262  
    */
263  
    auto wait()
263  
    auto wait()
264  
    {
264  
    {
265  
        return wait_awaitable(*this);
265  
        return wait_awaitable(*this);
266  
    }
266  
    }
267  

267  

268  
private:
268  
private:
269  
    timer_impl& get() const noexcept
269  
    timer_impl& get() const noexcept
270  
    {
270  
    {
271  
        return *static_cast<timer_impl*>(impl_);
271  
        return *static_cast<timer_impl*>(impl_);
272  
    }
272  
    }
273  
};
273  
};
274  

274  

275  
} // namespace boost::corosio
275  
} // namespace boost::corosio
276  

276  

277  
#endif
277  
#endif