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 9059x explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
53
54 9035x bool await_ready() const noexcept
55 {
56 9035x return token_.stop_requested();
57 }
58
59 9043x capy::io_result<> await_resume() const noexcept
60 {
61 9043x if (token_.stop_requested())
62 return {capy::error::canceled};
63 9043x return {ec_};
64 }
65
66 9059x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
67 -> std::coroutine_handle<>
68 {
69 9059x token_ = env->stop_token;
70 9059x cont_op_.cont.h = h;
71 9059x auto& impl = t_.get();
72 // Inline fast path: already expired and not in the heap
73 18096x if (impl.heap_index_ == implementation::npos &&
74 18070x (impl.expiry_ == (time_point::min)() ||
75 18092x impl.expiry_ <= clock_type::now()))
76 {
77 198x ec_ = {};
78 198x token_ = {}; // match normal path so await_resume
79 // returns ec_, not a stale stop check
80 198x auto d = env->executor;
81 198x d.post(cont_op_.cont);
82 198x return std::noop_coroutine();
83 }
84 8861x 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 9059x auto wait()
168 {
169 9059x 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 9062x 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 9117x implementation& get() const noexcept
197 {
198 9117x return *static_cast<implementation*>(h_.get());
199 }
200 };
201
202 } // namespace boost::corosio
203
204 #endif
205