include/boost/corosio/io/io_timer.hpp

96.7% Lines (29/30) 100.0% Functions (10/10) 93.8% Branches (15/16)
include/boost/corosio/io/io_timer.hpp
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
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 2102 explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
51
52 2102 bool await_ready() const noexcept
53 {
54 2102 return token_.stop_requested();
55 }
56
57 2102 capy::io_result<> await_resume() const noexcept
58 {
59
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2102 times.
2102 if (token_.stop_requested())
60 return {capy::error::canceled};
61 2102 return {ec_};
62 }
63
64 2102 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
65 -> std::coroutine_handle<>
66 {
67 2102 token_ = env->stop_token;
68 2102 auto& impl = t_.get();
69 // Inline fast path: already expired and not in the heap
70
2/2
✓ Branch 4 → 5 taken 2091 times.
✓ Branch 4 → 13 taken 11 times.
4193 if (impl.heap_index_ == implementation::npos &&
71
5/5
✓ Branch 6 → 7 taken 2091 times.
✓ Branch 7 → 8 taken 2089 times.
✓ Branch 7 → 12 taken 2 times.
✓ Branch 11 → 12 taken 79 times.
✓ Branch 11 → 13 taken 2010 times.
4180 (impl.expiry_ == (time_point::min)() ||
72
3/3
✓ Branch 9 → 10 taken 2089 times.
✓ Branch 14 → 15 taken 81 times.
✓ Branch 14 → 20 taken 2021 times.
4191 impl.expiry_ <= clock_type::now()))
73 {
74 81 ec_ = {};
75 81 auto d = env->executor;
76
1/1
✓ Branch 16 → 17 taken 81 times.
81 d.post(h);
77 81 return std::noop_coroutine();
78 }
79
1/1
✓ Branch 22 → 23 taken 2021 times.
2021 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 733 std::size_t cancel()
117 {
118
2/2
✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 5 taken 727 times.
733 if (!get().might_have_pending_waits_)
119 6 return 0;
120 727 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 24 time_point expiry() const noexcept
129 {
130 24 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 This timer must outlive the returned awaitable.
145
146 @return An awaitable that completes with `io_result<>`.
147 */
148 2102 auto wait()
149 {
150 2102 return wait_awaitable(*this);
151 }
152
153 protected:
154 /** Dispatch cancel to the concrete implementation.
155
156 @return The number of operations that were cancelled.
157 */
158 virtual std::size_t do_cancel() = 0;
159
160 2113 explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
161
162 /// Move construct.
163 1 io_timer(io_timer&& other) noexcept : io_object(std::move(other)) {}
164
165 /// Move assign.
166 io_timer& operator=(io_timer&& other) noexcept
167 {
168 if (this != &other)
169 h_ = std::move(other.h_);
170 return *this;
171 }
172
173 io_timer(io_timer const&) = delete;
174 io_timer& operator=(io_timer const&) = delete;
175
176 /// Return the underlying implementation.
177 2859 implementation& get() const noexcept
178 {
179 2859 return *static_cast<implementation*>(h_.get());
180 }
181 };
182
183 } // namespace boost::corosio
184
185 #endif
186