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_IO_IO_TIMER_HPP
11  
#ifndef BOOST_COROSIO_IO_IO_TIMER_HPP
12  
#define BOOST_COROSIO_IO_IO_TIMER_HPP
12  
#define BOOST_COROSIO_IO_IO_TIMER_HPP
13  

13  

14  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
15  
#include <boost/corosio/io/io_object.hpp>
15  
#include <boost/corosio/io/io_object.hpp>
16  
#include <boost/capy/io_result.hpp>
16  
#include <boost/capy/io_result.hpp>
17  
#include <boost/capy/error.hpp>
17  
#include <boost/capy/error.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <boost/capy/ex/io_env.hpp>
19  
#include <boost/capy/ex/io_env.hpp>
20  

20  

21  
#include <chrono>
21  
#include <chrono>
22  
#include <coroutine>
22  
#include <coroutine>
23  
#include <cstddef>
23  
#include <cstddef>
24  
#include <limits>
24  
#include <limits>
25  
#include <stop_token>
25  
#include <stop_token>
26  
#include <system_error>
26  
#include <system_error>
27  

27  

28  
namespace boost::corosio {
28  
namespace boost::corosio {
29  

29  

30  
/** Abstract base for asynchronous timers.
30  
/** Abstract base for asynchronous timers.
31  

31  

32  
    Provides the common timer interface: `wait`, `cancel`, and
32  
    Provides the common timer interface: `wait`, `cancel`, and
33  
    `expiry`. Concrete classes like @ref timer add the ability
33  
    `expiry`. Concrete classes like @ref timer add the ability
34  
    to set expiry times and cancel individual waiters.
34  
    to set expiry times and cancel individual waiters.
35  

35  

36  
    @par Thread Safety
36  
    @par Thread Safety
37  
    Distinct objects: Safe.
37  
    Distinct objects: Safe.
38  
    Shared objects: Unsafe.
38  
    Shared objects: Unsafe.
39  

39  

40  
    @see timer, io_object
40  
    @see timer, io_object
41  
*/
41  
*/
42  
class BOOST_COROSIO_DECL io_timer : public io_object
42  
class BOOST_COROSIO_DECL io_timer : public io_object
43  
{
43  
{
44  
    struct wait_awaitable
44  
    struct wait_awaitable
45  
    {
45  
    {
46  
        io_timer& t_;
46  
        io_timer& t_;
47  
        std::stop_token token_;
47  
        std::stop_token token_;
48  
        mutable std::error_code ec_;
48  
        mutable std::error_code ec_;
49  

49  

50  
        explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
50  
        explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
51  

51  

52  
        bool await_ready() const noexcept
52  
        bool await_ready() const noexcept
53  
        {
53  
        {
54  
            return token_.stop_requested();
54  
            return token_.stop_requested();
55  
        }
55  
        }
56  

56  

57  
        capy::io_result<> await_resume() const noexcept
57  
        capy::io_result<> await_resume() const noexcept
58  
        {
58  
        {
59  
            if (token_.stop_requested())
59  
            if (token_.stop_requested())
60  
                return {capy::error::canceled};
60  
                return {capy::error::canceled};
61  
            return {ec_};
61  
            return {ec_};
62  
        }
62  
        }
63  

63  

64  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
64  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
65  
            -> std::coroutine_handle<>
65  
            -> std::coroutine_handle<>
66  
        {
66  
        {
67  
            token_     = env->stop_token;
67  
            token_     = env->stop_token;
68  
            auto& impl = t_.get();
68  
            auto& impl = t_.get();
69  
            // Inline fast path: already expired and not in the heap
69  
            // Inline fast path: already expired and not in the heap
70  
            if (impl.heap_index_ == implementation::npos &&
70  
            if (impl.heap_index_ == implementation::npos &&
71  
                (impl.expiry_ == (time_point::min)() ||
71  
                (impl.expiry_ == (time_point::min)() ||
72  
                 impl.expiry_ <= clock_type::now()))
72  
                 impl.expiry_ <= clock_type::now()))
73  
            {
73  
            {
74  
                ec_    = {};
74  
                ec_    = {};
75  
                token_ = {};  // match normal path so await_resume
75  
                token_ = {};  // match normal path so await_resume
76  
                              // returns ec_, not a stale stop check
76  
                              // returns ec_, not a stale stop check
77  
                auto d = env->executor;
77  
                auto d = env->executor;
78  
                d.post(h);
78  
                d.post(h);
79  
                return std::noop_coroutine();
79  
                return std::noop_coroutine();
80  
            }
80  
            }
81  
            return impl.wait(h, env->executor, std::move(token_), &ec_);
81  
            return impl.wait(h, env->executor, std::move(token_), &ec_);
82  
        }
82  
        }
83  
    };
83  
    };
84  

84  

85  
public:
85  
public:
86  
    struct implementation : io_object::implementation
86  
    struct implementation : io_object::implementation
87  
    {
87  
    {
88  
        static constexpr std::size_t npos =
88  
        static constexpr std::size_t npos =
89  
            (std::numeric_limits<std::size_t>::max)();
89  
            (std::numeric_limits<std::size_t>::max)();
90  

90  

91  
        std::chrono::steady_clock::time_point expiry_{};
91  
        std::chrono::steady_clock::time_point expiry_{};
92  
        std::size_t heap_index_        = npos;
92  
        std::size_t heap_index_        = npos;
93  
        bool might_have_pending_waits_ = false;
93  
        bool might_have_pending_waits_ = false;
94  

94  

95  
        virtual std::coroutine_handle<> wait(
95  
        virtual std::coroutine_handle<> wait(
96  
            std::coroutine_handle<>,
96  
            std::coroutine_handle<>,
97  
            capy::executor_ref,
97  
            capy::executor_ref,
98  
            std::stop_token,
98  
            std::stop_token,
99  
            std::error_code*) = 0;
99  
            std::error_code*) = 0;
100  
    };
100  
    };
101  

101  

102  
    /// The clock type used for time operations.
102  
    /// The clock type used for time operations.
103  
    using clock_type = std::chrono::steady_clock;
103  
    using clock_type = std::chrono::steady_clock;
104  

104  

105  
    /// The time point type for absolute expiry times.
105  
    /// The time point type for absolute expiry times.
106  
    using time_point = clock_type::time_point;
106  
    using time_point = clock_type::time_point;
107  

107  

108  
    /// The duration type for relative expiry times.
108  
    /// The duration type for relative expiry times.
109  
    using duration = clock_type::duration;
109  
    using duration = clock_type::duration;
110  

110  

111  
    /** Cancel all pending asynchronous wait operations.
111  
    /** Cancel all pending asynchronous wait operations.
112  

112  

113  
        All outstanding operations complete with an error code that
113  
        All outstanding operations complete with an error code that
114  
        compares equal to `capy::cond::canceled`.
114  
        compares equal to `capy::cond::canceled`.
115  

115  

116  
        @return The number of operations that were cancelled.
116  
        @return The number of operations that were cancelled.
117  
    */
117  
    */
118  
    std::size_t cancel()
118  
    std::size_t cancel()
119  
    {
119  
    {
120  
        if (!get().might_have_pending_waits_)
120  
        if (!get().might_have_pending_waits_)
121  
            return 0;
121  
            return 0;
122  
        return do_cancel();
122  
        return do_cancel();
123  
    }
123  
    }
124  

124  

125  
    /** Return the timer's expiry time as an absolute time.
125  
    /** Return the timer's expiry time as an absolute time.
126  

126  

127  
        @return The expiry time point. If no expiry has been set,
127  
        @return The expiry time point. If no expiry has been set,
128  
            returns a default-constructed time_point.
128  
            returns a default-constructed time_point.
129  
    */
129  
    */
130  
    time_point expiry() const noexcept
130  
    time_point expiry() const noexcept
131  
    {
131  
    {
132  
        return get().expiry_;
132  
        return get().expiry_;
133  
    }
133  
    }
134  

134  

135  
    /** Wait for the timer to expire.
135  
    /** Wait for the timer to expire.
136  

136  

137  
        Multiple coroutines may wait on the same timer concurrently.
137  
        Multiple coroutines may wait on the same timer concurrently.
138  
        When the timer expires, all waiters complete with success.
138  
        When the timer expires, all waiters complete with success.
139  

139  

140  
        The operation supports cancellation via `std::stop_token` through
140  
        The operation supports cancellation via `std::stop_token` through
141  
        the affine awaitable protocol. If the associated stop token is
141  
        the affine awaitable protocol. If the associated stop token is
142  
        triggered, only that waiter completes with an error that
142  
        triggered, only that waiter completes with an error that
143  
        compares equal to `capy::cond::canceled`; other waiters are
143  
        compares equal to `capy::cond::canceled`; other waiters are
144  
        unaffected.
144  
        unaffected.
145  

145  

146  
        This timer must outlive the returned awaitable.
146  
        This timer must outlive the returned awaitable.
147  

147  

148  
        @return An awaitable that completes with `io_result<>`.
148  
        @return An awaitable that completes with `io_result<>`.
149  
    */
149  
    */
150  
    auto wait()
150  
    auto wait()
151  
    {
151  
    {
152  
        return wait_awaitable(*this);
152  
        return wait_awaitable(*this);
153  
    }
153  
    }
154  

154  

155  
protected:
155  
protected:
156  
    /** Dispatch cancel to the concrete implementation.
156  
    /** Dispatch cancel to the concrete implementation.
157  

157  

158  
        @return The number of operations that were cancelled.
158  
        @return The number of operations that were cancelled.
159  
    */
159  
    */
160  
    virtual std::size_t do_cancel() = 0;
160  
    virtual std::size_t do_cancel() = 0;
161  

161  

162  
    explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
162  
    explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
163  

163  

164  
    /// Move construct.
164  
    /// Move construct.
165  
    io_timer(io_timer&& other) noexcept : io_object(std::move(other)) {}
165  
    io_timer(io_timer&& other) noexcept : io_object(std::move(other)) {}
166  

166  

167  
    /// Move assign.
167  
    /// Move assign.
168  
    io_timer& operator=(io_timer&& other) noexcept
168  
    io_timer& operator=(io_timer&& other) noexcept
169  
    {
169  
    {
170  
        if (this != &other)
170  
        if (this != &other)
171  
            h_ = std::move(other.h_);
171  
            h_ = std::move(other.h_);
172  
        return *this;
172  
        return *this;
173  
    }
173  
    }
174  

174  

175  
    io_timer(io_timer const&)            = delete;
175  
    io_timer(io_timer const&)            = delete;
176  
    io_timer& operator=(io_timer const&) = delete;
176  
    io_timer& operator=(io_timer const&) = delete;
177  

177  

178  
    /// Return the underlying implementation.
178  
    /// Return the underlying implementation.
179  
    implementation& get() const noexcept
179  
    implementation& get() const noexcept
180  
    {
180  
    {
181  
        return *static_cast<implementation*>(h_.get());
181  
        return *static_cast<implementation*>(h_.get());
182  
    }
182  
    }
183  
};
183  
};
184  

184  

185  
} // namespace boost::corosio
185  
} // namespace boost::corosio
186  

186  

187  
#endif
187  
#endif