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

30  

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

32  

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

34  

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

38  

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

43  

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

46  

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

50  

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

63  

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

65  

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

70  

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

77  

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

98  

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

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

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

115  

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

119  

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

122  

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

125  

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

127  

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

131  

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

133  

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

137  

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

139  

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

144  

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

146  

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

158  

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

160  

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

162  

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

166  

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

168  

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

171  

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

173  

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

175  

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

179  

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

182  

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

184  

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

187  

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

196  

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

198  

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

202  

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

211  

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

213  

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

221  

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

223  

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

225  

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

227  

195  
        @return The number of pending operations that were cancelled.
228  
        @return The number of pending operations that were cancelled.
196  
    */
229  
    */
197 -
    std::size_t expires_at(time_point t);
230 +
    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 +
    }
198  

239  

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

241  

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

243  

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

245  

205  
        @return The number of pending operations that were cancelled.
246  
        @return The number of pending operations that were cancelled.
206  
    */
247  
    */
207 -
    std::size_t expires_after(duration d);
248 +
    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 +
    }
208  

260  

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

262  

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

266  

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

268  

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

276  

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

278  

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

281  

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

287  

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

305  

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

310  

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

319  

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

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

333  

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

335  

277  
#endif
336  
#endif