1 +
//
 
2 +
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
 
3 +
// Copyright (c) 2026 Steve Gerbino
 
4 +
//
 
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)
 
7 +
//
 
8 +
// Official repository: https://github.com/cppalliance/corosio
 
9 +
//
 
10 +

 
11 +
#ifndef BOOST_COROSIO_IO_IO_TIMER_HPP
 
12 +
#define BOOST_COROSIO_IO_IO_TIMER_HPP
 
13 +

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

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

 
28 +
namespace boost::corosio {
 
29 +

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

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

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

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

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

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

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

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

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

 
89 +
        std::chrono::steady_clock::time_point expiry_{};
 
90 +
        std::size_t heap_index_        = npos;
 
91 +
        bool might_have_pending_waits_ = false;
 
92 +

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

 
100 +
    /// The clock type used for time operations.
 
101 +
    using clock_type = std::chrono::steady_clock;
 
102 +

 
103 +
    /// The time point type for absolute expiry times.
 
104 +
    using time_point = clock_type::time_point;
 
105 +

 
106 +
    /// The duration type for relative expiry times.
 
107 +
    using duration = clock_type::duration;
 
108 +

 
109 +
    /** Cancel all pending asynchronous wait operations.
 
110 +

 
111 +
        All outstanding operations complete with an error code that
 
112 +
        compares equal to `capy::cond::canceled`.
 
113 +

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

 
123 +
    /** Return the timer's expiry time as an absolute time.
 
124 +

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

 
133 +
    /** Wait for the timer to expire.
 
134 +

 
135 +
        Multiple coroutines may wait on the same timer concurrently.
 
136 +
        When the timer expires, all waiters complete with success.
 
137 +

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

 
144 +
        @return An awaitable that completes with `io_result<>`.
 
145 +
    */
 
146 +
    auto wait()
 
147 +
    {
 
148 +
        return wait_awaitable(*this);
 
149 +
    }
 
150 +

 
151 +
protected:
 
152 +
    /** Dispatch cancel to the concrete implementation.
 
153 +

 
154 +
        @return The number of operations that were cancelled.
 
155 +
    */
 
156 +
    virtual std::size_t do_cancel() = 0;
 
157 +

 
158 +
    explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
 
159 +

 
160 +
    /// Move construct.
 
161 +
    io_timer(io_timer&& other) noexcept : io_object(std::move(other)) {}
 
162 +

 
163 +
    /// Move assign.
 
164 +
    io_timer& operator=(io_timer&& other) noexcept
 
165 +
    {
 
166 +
        if (this != &other)
 
167 +
            h_ = std::move(other.h_);
 
168 +
        return *this;
 
169 +
    }
 
170 +

 
171 +
    io_timer(io_timer const&)            = delete;
 
172 +
    io_timer& operator=(io_timer const&) = delete;
 
173 +

 
174 +
    /// Return the underlying implementation.
 
175 +
    implementation& get() const noexcept
 
176 +
    {
 
177 +
        return *static_cast<implementation*>(h_.get());
 
178 +
    }
 
179 +
};
 
180 +

 
181 +
} // namespace boost::corosio
 
182 +

 
183 +
#endif