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

23  

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

95  

99  
public:
96  
public:
100  
    struct implementation : io_object::implementation
97  
    struct implementation : io_object::implementation
101  
    {
98  
    {
102  
        static constexpr std::size_t npos =
99  
        static constexpr std::size_t npos =
103  
            (std::numeric_limits<std::size_t>::max)();
100  
            (std::numeric_limits<std::size_t>::max)();
104  

101  

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

105  

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

112  

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

116  

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

119  

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

122  

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

124  

128  
        Cancels any pending operations and releases timer resources.
125  
        Cancels any pending operations and releases timer resources.
129  
    */
126  
    */
130 -
    ~timer();
127 +
    ~timer() override;
131  

128  

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

130  

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

134  

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

136  

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

141  

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

143  

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

153  

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

155  

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

157  

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

161  

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

163  

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

165  

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

167  

174 -

 
175 -
        @throws std::logic_error if the timers have different execution contexts.
 
176  
        @return Reference to this timer.
168  
        @return Reference to this timer.
177  
    */
169  
    */
178 -
    timer& operator=(timer&& other);
170 +
    timer& operator=(timer&& other) noexcept;
179  

171  

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

174  

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

176  

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

179  

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

188  

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

190  

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

194  

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

203  

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

205  

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

213  

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

215  

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

217  

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

219  

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

231  

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

233  

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

235  

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

237  

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

252  

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

254  

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

258  

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

260  

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

268  

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

270  

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

273  

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

279  

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

297  

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

302  

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

311  

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

318  

327  
    /// Return the underlying implementation.
319  
    /// Return the underlying implementation.
328  
    implementation& get() const noexcept
320  
    implementation& get() const noexcept
329  
    {
321  
    {
330  
        return *static_cast<implementation*>(h_.get());
322  
        return *static_cast<implementation*>(h_.get());
331  
    }
323  
    }
332  
};
324  
};
333  

325  

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

327  

336  
#endif
328  
#endif