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 <limits>
28  
#include <limits>
29  
#include <stop_token>
29  
#include <stop_token>
30  

30  

31  
namespace boost::corosio {
31  
namespace boost::corosio {
32  

32  

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

34  

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

38  

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

43  

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

46  

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

50  

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

63  

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

65  

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

70  

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

77  

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

98  

99  
public:
99  
public:
100  
    struct timer_impl : io_object_impl
100  
    struct timer_impl : io_object_impl
101  
    {
101  
    {
102  
        static constexpr std::size_t npos =
102  
        static constexpr std::size_t npos =
103  
            (std::numeric_limits<std::size_t>::max)();
103  
            (std::numeric_limits<std::size_t>::max)();
104  

104  

105  
        std::chrono::steady_clock::time_point expiry_{};
105  
        std::chrono::steady_clock::time_point expiry_{};
106  
        std::size_t heap_index_ = npos;
106  
        std::size_t heap_index_ = npos;
107  
        bool might_have_pending_waits_ = false;
107  
        bool might_have_pending_waits_ = false;
108  

108  

109  
        virtual std::coroutine_handle<> wait(
109  
        virtual std::coroutine_handle<> wait(
110  
            std::coroutine_handle<>,
110  
            std::coroutine_handle<>,
111  
            capy::executor_ref,
111  
            capy::executor_ref,
112  
            std::stop_token,
112  
            std::stop_token,
113  
            std::error_code*) = 0;
113  
            std::error_code*) = 0;
114  
    };
114  
    };
115  

115  

116  
public:
116  
public:
117  
    /// The clock type used for time operations.
117  
    /// The clock type used for time operations.
118  
    using clock_type = std::chrono::steady_clock;
118  
    using clock_type = std::chrono::steady_clock;
119  

119  

120  
    /// The time point type for absolute expiry times.
120  
    /// The time point type for absolute expiry times.
121  
    using time_point = clock_type::time_point;
121  
    using time_point = clock_type::time_point;
122  

122  

123  
    /// The duration type for relative expiry times.
123  
    /// The duration type for relative expiry times.
124  
    using duration = clock_type::duration;
124  
    using duration = clock_type::duration;
125  

125  

126  
    /** Destructor.
126  
    /** Destructor.
127  

127  

128  
        Cancels any pending operations and releases timer resources.
128  
        Cancels any pending operations and releases timer resources.
129  
    */
129  
    */
130  
    ~timer();
130  
    ~timer();
131  

131  

132  
    /** Construct a timer from an execution context.
132  
    /** Construct a timer from an execution context.
133  

133  

134  
        @param ctx The execution context that will own this timer.
134  
        @param ctx The execution context that will own this timer.
135  
    */
135  
    */
136  
    explicit timer(capy::execution_context& ctx);
136  
    explicit timer(capy::execution_context& ctx);
137  

137  

138  
    /** Construct a timer with an initial absolute expiry time.
138  
    /** Construct a timer with an initial absolute expiry time.
139  

139  

140  
        @param ctx The execution context that will own this timer.
140  
        @param ctx The execution context that will own this timer.
141  
        @param t The initial expiry time point.
141  
        @param t The initial expiry time point.
142  
    */
142  
    */
143  
    timer(capy::execution_context& ctx, time_point t);
143  
    timer(capy::execution_context& ctx, time_point t);
144  

144  

145  
    /** Construct a timer with an initial relative expiry time.
145  
    /** Construct a timer with an initial relative expiry time.
146  

146  

147  
        @param ctx The execution context that will own this timer.
147  
        @param ctx The execution context that will own this timer.
148  
        @param d The initial expiry duration relative to now.
148  
        @param d The initial expiry duration relative to now.
149  
    */
149  
    */
150  
    template<class Rep, class Period>
150  
    template<class Rep, class Period>
151  
    timer(
151  
    timer(
152  
        capy::execution_context& ctx,
152  
        capy::execution_context& ctx,
153  
        std::chrono::duration<Rep, Period> d)
153  
        std::chrono::duration<Rep, Period> d)
154  
        : timer(ctx)
154  
        : timer(ctx)
155  
    {
155  
    {
156  
        expires_after(d);
156  
        expires_after(d);
157  
    }
157  
    }
158  

158  

159  
    /** Move constructor.
159  
    /** Move constructor.
160  

160  

161  
        Transfers ownership of the timer resources.
161  
        Transfers ownership of the timer resources.
162  

162  

163  
        @param other The timer to move from.
163  
        @param other The timer to move from.
164  
    */
164  
    */
165  
    timer(timer&& other) noexcept;
165  
    timer(timer&& other) noexcept;
166  

166  

167  
    /** Move assignment operator.
167  
    /** Move assignment operator.
168  

168  

169  
        Closes any existing timer and transfers ownership.
169  
        Closes any existing timer and transfers ownership.
170  
        The source and destination must share the same execution context.
170  
        The source and destination must share the same execution context.
171  

171  

172  
        @param other The timer to move from.
172  
        @param other The timer to move from.
173  

173  

174  
        @return Reference to this timer.
174  
        @return Reference to this timer.
175  

175  

176  
        @throws std::logic_error if the timers have different execution contexts.
176  
        @throws std::logic_error if the timers have different execution contexts.
177  
    */
177  
    */
178  
    timer& operator=(timer&& other);
178  
    timer& operator=(timer&& other);
179  

179  

180  
    timer(timer const&) = delete;
180  
    timer(timer const&) = delete;
181  
    timer& operator=(timer const&) = delete;
181  
    timer& operator=(timer const&) = delete;
182  

182  

183  
    /** Cancel all pending asynchronous wait operations.
183  
    /** Cancel all pending asynchronous wait operations.
184  

184  

185  
        All outstanding operations complete with an error code that
185  
        All outstanding operations complete with an error code that
186  
        compares equal to `capy::cond::canceled`.
186  
        compares equal to `capy::cond::canceled`.
187  

187  

188  
        @return The number of operations that were cancelled.
188  
        @return The number of operations that were cancelled.
189  
    */
189  
    */
190  
    std::size_t cancel()
190  
    std::size_t cancel()
191  
    {
191  
    {
192  
        if (!get().might_have_pending_waits_)
192  
        if (!get().might_have_pending_waits_)
193  
            return 0;
193  
            return 0;
194  
        return do_cancel();
194  
        return do_cancel();
195  
    }
195  
    }
196  

196  

197  
    /** Cancel one pending asynchronous wait operation.
197  
    /** Cancel one pending asynchronous wait operation.
198  

198  

199  
        The oldest pending wait is cancelled (FIFO order). It
199  
        The oldest pending wait is cancelled (FIFO order). It
200  
        completes with an error code that compares equal to
200  
        completes with an error code that compares equal to
201  
        `capy::cond::canceled`.
201  
        `capy::cond::canceled`.
202  

202  

203  
        @return The number of operations that were cancelled (0 or 1).
203  
        @return The number of operations that were cancelled (0 or 1).
204  
    */
204  
    */
205  
    std::size_t cancel_one()
205  
    std::size_t cancel_one()
206  
    {
206  
    {
207  
        if (!get().might_have_pending_waits_)
207  
        if (!get().might_have_pending_waits_)
208  
            return 0;
208  
            return 0;
209  
        return do_cancel_one();
209  
        return do_cancel_one();
210  
    }
210  
    }
211  

211  

212  
    /** Return the timer's expiry time as an absolute time.
212  
    /** Return the timer's expiry time as an absolute time.
213  

213  

214  
        @return The expiry time point. If no expiry has been set,
214  
        @return The expiry time point. If no expiry has been set,
215  
            returns a default-constructed time_point.
215  
            returns a default-constructed time_point.
216  
    */
216  
    */
217  
    time_point expiry() const noexcept
217  
    time_point expiry() const noexcept
218  
    {
218  
    {
219  
        return get().expiry_;
219  
        return get().expiry_;
220  
    }
220  
    }
221  

221  

222  
    /** Set the timer's expiry time as an absolute time.
222  
    /** Set the timer's expiry time as an absolute time.
223  

223  

224  
        Any pending asynchronous wait operations will be cancelled.
224  
        Any pending asynchronous wait operations will be cancelled.
225  

225  

226  
        @param t The expiry time to be used for the timer.
226  
        @param t The expiry time to be used for the timer.
227  

227  

228  
        @return The number of pending operations that were cancelled.
228  
        @return The number of pending operations that were cancelled.
229  
    */
229  
    */
230  
    std::size_t expires_at(time_point t)
230  
    std::size_t expires_at(time_point t)
231  
    {
231  
    {
232  
        auto& impl = get();
232  
        auto& impl = get();
233  
        impl.expiry_ = t;
233  
        impl.expiry_ = t;
234  
        if (impl.heap_index_ == timer_impl::npos &&
234  
        if (impl.heap_index_ == timer_impl::npos &&
235  
            !impl.might_have_pending_waits_)
235  
            !impl.might_have_pending_waits_)
236  
            return 0;
236  
            return 0;
237  
        return do_update_expiry();
237  
        return do_update_expiry();
238  
    }
238  
    }
239  

239  

240  
    /** Set the timer's expiry time relative to now.
240  
    /** Set the timer's expiry time relative to now.
241  

241  

242  
        Any pending asynchronous wait operations will be cancelled.
242  
        Any pending asynchronous wait operations will be cancelled.
243  

243  

244  
        @param d The expiry time relative to now.
244  
        @param d The expiry time relative to now.
245  

245  

246  
        @return The number of pending operations that were cancelled.
246  
        @return The number of pending operations that were cancelled.
247  
    */
247  
    */
248  
    std::size_t expires_after(duration d)
248  
    std::size_t expires_after(duration d)
249  
    {
249  
    {
250  
        auto& impl = get();
250  
        auto& impl = get();
251  
        if (d <= duration::zero())
251  
        if (d <= duration::zero())
252  
            impl.expiry_ = (time_point::min)();
252  
            impl.expiry_ = (time_point::min)();
253  
        else
253  
        else
254  
            impl.expiry_ = clock_type::now() + d;
254  
            impl.expiry_ = clock_type::now() + d;
255  
        if (impl.heap_index_ == timer_impl::npos &&
255  
        if (impl.heap_index_ == timer_impl::npos &&
256  
            !impl.might_have_pending_waits_)
256  
            !impl.might_have_pending_waits_)
257  
            return 0;
257  
            return 0;
258  
        return do_update_expiry();
258  
        return do_update_expiry();
259  
    }
259  
    }
260  

260  

261  
    /** Set the timer's expiry time relative to now.
261  
    /** Set the timer's expiry time relative to now.
262  

262  

263  
        This is a convenience overload that accepts any duration type
263  
        This is a convenience overload that accepts any duration type
264  
        and converts it to the timer's native duration type. Any
264  
        and converts it to the timer's native duration type. Any
265  
        pending asynchronous wait operations will be cancelled.
265  
        pending asynchronous wait operations will be cancelled.
266  

266  

267  
        @param d The expiry time relative to now.
267  
        @param d The expiry time relative to now.
268  

268  

269  
        @return The number of pending operations that were cancelled.
269  
        @return The number of pending operations that were cancelled.
270  
    */
270  
    */
271  
    template<class Rep, class Period>
271  
    template<class Rep, class Period>
272  
    std::size_t expires_after(std::chrono::duration<Rep, Period> d)
272  
    std::size_t expires_after(std::chrono::duration<Rep, Period> d)
273  
    {
273  
    {
274  
        return expires_after(std::chrono::duration_cast<duration>(d));
274  
        return expires_after(std::chrono::duration_cast<duration>(d));
275  
    }
275  
    }
276  

276  

277  
    /** Wait for the timer to expire.
277  
    /** Wait for the timer to expire.
278  

278  

279  
        Multiple coroutines may wait on the same timer concurrently.
279  
        Multiple coroutines may wait on the same timer concurrently.
280  
        When the timer expires, all waiters complete with success.
280  
        When the timer expires, all waiters complete with success.
281  

281  

282  
        The operation supports cancellation via `std::stop_token` through
282  
        The operation supports cancellation via `std::stop_token` through
283  
        the affine awaitable protocol. If the associated stop token is
283  
        the affine awaitable protocol. If the associated stop token is
284  
        triggered, only that waiter completes with an error that
284  
        triggered, only that waiter completes with an error that
285  
        compares equal to `capy::cond::canceled`; other waiters are
285  
        compares equal to `capy::cond::canceled`; other waiters are
286  
        unaffected.
286  
        unaffected.
287  

287  

288  
        @par Example
288  
        @par Example
289  
        @code
289  
        @code
290  
        timer t(ctx);
290  
        timer t(ctx);
291  
        t.expires_after(std::chrono::seconds(5));
291  
        t.expires_after(std::chrono::seconds(5));
292  
        auto [ec] = co_await t.wait();
292  
        auto [ec] = co_await t.wait();
293  
        if (ec == capy::cond::canceled)
293  
        if (ec == capy::cond::canceled)
294  
        {
294  
        {
295  
            // Cancelled via stop_token or cancel()
295  
            // Cancelled via stop_token or cancel()
296  
            co_return;
296  
            co_return;
297  
        }
297  
        }
298  
        if (ec)
298  
        if (ec)
299  
        {
299  
        {
300  
            // Handle other errors
300  
            // Handle other errors
301  
            co_return;
301  
            co_return;
302  
        }
302  
        }
303  
        // Timer expired
303  
        // Timer expired
304  
        @endcode
304  
        @endcode
305  

305  

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

310  

311  
        @par Preconditions
311  
        @par Preconditions
312  
        The timer must have an expiry time set via expires_at() or
312  
        The timer must have an expiry time set via expires_at() or
313  
        expires_after().
313  
        expires_after().
314  
    */
314  
    */
315  
    auto wait()
315  
    auto wait()
316  
    {
316  
    {
317  
        return wait_awaitable(*this);
317  
        return wait_awaitable(*this);
318  
    }
318  
    }
319  

319  

320  
private:
320  
private:
321  
    // Out-of-line cancel/expiry when inline fast-path
321  
    // Out-of-line cancel/expiry when inline fast-path
322  
    // conditions (no waiters, not in heap) are not met.
322  
    // conditions (no waiters, not in heap) are not met.
323  
    std::size_t do_cancel();
323  
    std::size_t do_cancel();
324  
    std::size_t do_cancel_one();
324  
    std::size_t do_cancel_one();
325  
    std::size_t do_update_expiry();
325  
    std::size_t do_update_expiry();
326  

326  

327  
    /// Return the underlying implementation.
327  
    /// Return the underlying implementation.
328  
    timer_impl& get() const noexcept
328  
    timer_impl& get() const noexcept
329  
    {
329  
    {
330  
        return *static_cast<timer_impl*>(impl_);
330  
        return *static_cast<timer_impl*>(impl_);
331  
    }
331  
    }
332  
};
332  
};
333  

333  

334  
} // namespace boost::corosio
334  
} // namespace boost::corosio
335  

335  

336  
#endif
336  
#endif