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

29  

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

31  

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

33  

35  
    This class provides asynchronous timer operations that return
34  
    This class provides asynchronous timer operations that return
36  
    awaitable types. The timer can be used to schedule operations
35  
    awaitable types. The timer can be used to schedule operations
37  
    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.
38  

37  

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

42  

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

45  

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

49  

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

62  

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

64  

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

69  

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

76  

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

85  

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

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

 
108  
    {
88  
    {
109  
        virtual std::coroutine_handle<> wait(
89  
        virtual std::coroutine_handle<> wait(
110  
            std::coroutine_handle<>,
90  
            std::coroutine_handle<>,
111  
            capy::executor_ref,
91  
            capy::executor_ref,
112  
            std::stop_token,
92  
            std::stop_token,
113  
            std::error_code*) = 0;
93  
            std::error_code*) = 0;
114  
    };
94  
    };
115  

95  

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

99  

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

102  

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

105  

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

107  

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

111  

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

113  

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

117  

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

119  

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

124  

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

126  

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

138  

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

140  

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

142  

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

146  

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

148  

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

151  

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

153  

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

155  

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

159  

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

162  

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

164  

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

167  

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

171  

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

173  

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

177  

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

181  

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

183  

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

188  

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

190  

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

192  

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

194  

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

198  

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

200  

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

202  

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

204  

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

208  

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

210  

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

214  

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

216  

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

224  

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

226  

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

229  

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

235  

288  
        @par Example
236  
        @par Example
289  
        @code
237  
        @code
290  
        timer t(ctx);
238  
        timer t(ctx);
291  
        t.expires_after(std::chrono::seconds(5));
239  
        t.expires_after(std::chrono::seconds(5));
292  
        auto [ec] = co_await t.wait();
240  
        auto [ec] = co_await t.wait();
293  
        if (ec == capy::cond::canceled)
241  
        if (ec == capy::cond::canceled)
294  
        {
242  
        {
295  
            // Cancelled via stop_token or cancel()
243  
            // Cancelled via stop_token or cancel()
296  
            co_return;
244  
            co_return;
297  
        }
245  
        }
298  
        if (ec)
246  
        if (ec)
299  
        {
247  
        {
300  
            // Handle other errors
248  
            // Handle other errors
301  
            co_return;
249  
            co_return;
302  
        }
250  
        }
303  
        // Timer expired
251  
        // Timer expired
304  
        @endcode
252  
        @endcode
305  

253  

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

258  

311  
        @par Preconditions
259  
        @par Preconditions
312  
        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
313  
        expires_after().
261  
        expires_after().
314  
    */
262  
    */
315  
    auto wait()
263  
    auto wait()
316  
    {
264  
    {
317  
        return wait_awaitable(*this);
265  
        return wait_awaitable(*this);
318  
    }
266  
    }
319  

267  

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

 
326 -
    /// Return the underlying implementation.
 
327  
private:
268  
private:
328  
    timer_impl& get() const noexcept
269  
    timer_impl& get() const noexcept
329  
    {
270  
    {
330  
        return *static_cast<timer_impl*>(impl_);
271  
        return *static_cast<timer_impl*>(impl_);
331  
    }
272  
    }
332  
};
273  
};
333  

274  

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

276  

336  
#endif
277  
#endif