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