include/boost/corosio/io/io_timer.hpp

96.8% Lines (30/31) 100.0% Functions (10/10)
Line TLA Hits Source Code
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 7598x explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
51
52 7574x bool await_ready() const noexcept
53 {
54 7574x return token_.stop_requested();
55 }
56
57 7588x capy::io_result<> await_resume() const noexcept
58 {
59 7588x if (token_.stop_requested())
60 return {capy::error::canceled};
61 7588x return {ec_};
62 }
63
64 7598x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
65 -> std::coroutine_handle<>
66 {
67 7598x token_ = env->stop_token;
68 7598x auto& impl = t_.get();
69 // Inline fast path: already expired and not in the heap
70 15174x if (impl.heap_index_ == implementation::npos &&
71 15148x (impl.expiry_ == (time_point::min)() ||
72 15170x impl.expiry_ <= clock_type::now()))
73 {
74 279x ec_ = {};
75 279x token_ = {}; // match normal path so await_resume
76 // returns ec_, not a stale stop check
77 279x auto d = env->executor;
78 279x d.post(h);
79 279x return std::noop_coroutine();
80 }
81 7319x return impl.wait(h, env->executor, std::move(token_), &ec_);
82 }
83 };
84
85 public:
86 /** Backend interface for timer wait operations.
87
88 Holds per-timer state (expiry, heap position) and provides
89 the virtual `wait` entry point that concrete timer services
90 override.
91 */
92 struct implementation : io_object::implementation
93 {
94 /// Sentinel value indicating the timer is not in the heap.
95 static constexpr std::size_t npos =
96 (std::numeric_limits<std::size_t>::max)();
97
98 /// The absolute expiry time point.
99 std::chrono::steady_clock::time_point expiry_{};
100
101 /// Index in the timer service's min-heap, or `npos`.
102 std::size_t heap_index_ = npos;
103
104 /// True if `wait()` has been called since last cancel.
105 bool might_have_pending_waits_ = false;
106
107 /// Initiate an asynchronous wait for the timer to expire.
108 virtual std::coroutine_handle<> wait(
109 std::coroutine_handle<>,
110 capy::executor_ref,
111 std::stop_token,
112 std::error_code*) = 0;
113 };
114
115 /// The clock type used for time operations.
116 using clock_type = std::chrono::steady_clock;
117
118 /// The time point type for absolute expiry times.
119 using time_point = clock_type::time_point;
120
121 /// The duration type for relative expiry times.
122 using duration = clock_type::duration;
123
124 /** Cancel all pending asynchronous wait operations.
125
126 All outstanding operations complete with an error code that
127 compares equal to `capy::cond::canceled`.
128
129 @return The number of operations that were cancelled.
130 */
131 20x std::size_t cancel()
132 {
133 20x if (!get().might_have_pending_waits_)
134 12x return 0;
135 8x return do_cancel();
136 }
137
138 /** Return the timer's expiry time as an absolute time.
139
140 @return The expiry time point. If no expiry has been set,
141 returns a default-constructed time_point.
142 */
143 38x time_point expiry() const noexcept
144 {
145 38x return get().expiry_;
146 }
147
148 /** Wait for the timer to expire.
149
150 Multiple coroutines may wait on the same timer concurrently.
151 When the timer expires, all waiters complete with success.
152
153 The operation supports cancellation via `std::stop_token` through
154 the affine awaitable protocol. If the associated stop token is
155 triggered, only that waiter completes with an error that
156 compares equal to `capy::cond::canceled`; other waiters are
157 unaffected.
158
159 This timer must outlive the returned awaitable.
160
161 @return An awaitable that completes with `io_result<>`.
162 */
163 7598x auto wait()
164 {
165 7598x return wait_awaitable(*this);
166 }
167
168 protected:
169 /** Dispatch cancel to the concrete implementation.
170
171 @return The number of operations that were cancelled.
172 */
173 virtual std::size_t do_cancel() = 0;
174
175 7601x explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
176
177 /// Move construct.
178 2x io_timer(io_timer&& other) noexcept : io_object(std::move(other)) {}
179
180 /// Move assign.
181 io_timer& operator=(io_timer&& other) noexcept
182 {
183 if (this != &other)
184 h_ = std::move(other.h_);
185 return *this;
186 }
187
188 io_timer(io_timer const&) = delete;
189 io_timer& operator=(io_timer const&) = delete;
190
191 /// Return the underlying implementation.
192 7656x implementation& get() const noexcept
193 {
194 7656x return *static_cast<implementation*>(h_.get());
195 }
196 };
197
198 } // namespace boost::corosio
199
200 #endif
201