include/boost/corosio/io/io_timer.hpp

96.9% Lines (31/32) 100.0% List of functions (10/10)
io_timer.hpp
f(x) Functions (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/detail/continuation_op.hpp>
16 #include <boost/corosio/io/io_object.hpp>
17 #include <boost/capy/io_result.hpp>
18 #include <boost/capy/error.hpp>
19 #include <boost/capy/ex/executor_ref.hpp>
20 #include <boost/capy/ex/io_env.hpp>
21
22 #include <chrono>
23 #include <coroutine>
24 #include <cstddef>
25 #include <limits>
26 #include <stop_token>
27 #include <system_error>
28
29 namespace boost::corosio {
30
31 /** Abstract base for asynchronous timers.
32
33 Provides the common timer interface: `wait`, `cancel`, and
34 `expiry`. Concrete classes like @ref timer add the ability
35 to set expiry times and cancel individual waiters.
36
37 @par Thread Safety
38 Distinct objects: Safe.
39 Shared objects: Unsafe.
40
41 @see timer, io_object
42 */
43 class BOOST_COROSIO_DECL io_timer : public io_object
44 {
45 struct wait_awaitable
46 {
47 io_timer& t_;
48 std::stop_token token_;
49 mutable std::error_code ec_;
50 detail::continuation_op cont_op_;
51
52 8622x explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
53
54 8598x bool await_ready() const noexcept
55 {
56 8598x return token_.stop_requested();
57 }
58
59 8606x capy::io_result<> await_resume() const noexcept
60 {
61 8606x if (token_.stop_requested())
62 return {capy::error::canceled};
63 8606x return {ec_};
64 }
65
66 8622x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
67 -> std::coroutine_handle<>
68 {
69 8622x token_ = env->stop_token;
70 8622x cont_op_.cont.h = h;
71 8622x auto& impl = t_.get();
72 // Inline fast path: already expired and not in the heap
73 17222x if (impl.heap_index_ == implementation::npos &&
74 17196x (impl.expiry_ == (time_point::min)() ||
75 17218x impl.expiry_ <= clock_type::now()))
76 {
77 202x ec_ = {};
78 202x token_ = {}; // match normal path so await_resume
79 // returns ec_, not a stale stop check
80 202x auto d = env->executor;
81 202x d.post(cont_op_.cont);
82 202x return std::noop_coroutine();
83 }
84 8420x return impl.wait(h, env->executor, std::move(token_), &ec_, &cont_op_.cont);
85 }
86 };
87
88 public:
89 /** Backend interface for timer wait operations.
90
91 Holds per-timer state (expiry, heap position) and provides
92 the virtual `wait` entry point that concrete timer services
93 override.
94 */
95 struct implementation : io_object::implementation
96 {
97 /// Sentinel value indicating the timer is not in the heap.
98 static constexpr std::size_t npos =
99 (std::numeric_limits<std::size_t>::max)();
100
101 /// The absolute expiry time point.
102 std::chrono::steady_clock::time_point expiry_{};
103
104 /// Index in the timer service's min-heap, or `npos`.
105 std::size_t heap_index_ = npos;
106
107 /// True if `wait()` has been called since last cancel.
108 bool might_have_pending_waits_ = false;
109
110 /// Initiate an asynchronous wait for the timer to expire.
111 virtual std::coroutine_handle<> wait(
112 std::coroutine_handle<>,
113 capy::executor_ref,
114 std::stop_token,
115 std::error_code*,
116 capy::continuation*) = 0;
117 };
118
119 /// The clock type used for time operations.
120 using clock_type = std::chrono::steady_clock;
121
122 /// The time point type for absolute expiry times.
123 using time_point = clock_type::time_point;
124
125 /// The duration type for relative expiry times.
126 using duration = clock_type::duration;
127
128 /** Cancel all pending asynchronous wait operations.
129
130 All outstanding operations complete with an error code that
131 compares equal to `capy::cond::canceled`.
132
133 @return The number of operations that were cancelled.
134 */
135 20x std::size_t cancel()
136 {
137 20x if (!get().might_have_pending_waits_)
138 12x return 0;
139 8x return do_cancel();
140 }
141
142 /** Return the timer's expiry time as an absolute time.
143
144 @return The expiry time point. If no expiry has been set,
145 returns a default-constructed time_point.
146 */
147 38x time_point expiry() const noexcept
148 {
149 38x return get().expiry_;
150 }
151
152 /** Wait for the timer to expire.
153
154 Multiple coroutines may wait on the same timer concurrently.
155 When the timer expires, all waiters complete with success.
156
157 The operation supports cancellation via `std::stop_token` through
158 the affine awaitable protocol. If the associated stop token is
159 triggered, only that waiter completes with an error that
160 compares equal to `capy::cond::canceled`; other waiters are
161 unaffected.
162
163 This timer must outlive the returned awaitable.
164
165 @return An awaitable that completes with `io_result<>`.
166 */
167 8622x auto wait()
168 {
169 8622x return wait_awaitable(*this);
170 }
171
172 protected:
173 /** Dispatch cancel to the concrete implementation.
174
175 @return The number of operations that were cancelled.
176 */
177 virtual std::size_t do_cancel() = 0;
178
179 8625x explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
180
181 /// Move construct.
182 2x io_timer(io_timer&& other) noexcept : io_object(std::move(other)) {}
183
184 /// Move assign.
185 io_timer& operator=(io_timer&& other) noexcept
186 {
187 if (this != &other)
188 h_ = std::move(other.h_);
189 return *this;
190 }
191
192 io_timer(io_timer const&) = delete;
193 io_timer& operator=(io_timer const&) = delete;
194
195 /// Return the underlying implementation.
196 8680x implementation& get() const noexcept
197 {
198 8680x return *static_cast<implementation*>(h_.get());
199 }
200 };
201
202 } // namespace boost::corosio
203
204 #endif
205