TLA Line data 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 HIT 8622 : explicit wait_awaitable(io_timer& t) noexcept : t_(t) {}
53 :
54 8598 : bool await_ready() const noexcept
55 : {
56 8598 : return token_.stop_requested();
57 : }
58 :
59 8606 : capy::io_result<> await_resume() const noexcept
60 : {
61 8606 : if (token_.stop_requested())
62 MIS 0 : return {capy::error::canceled};
63 HIT 8606 : return {ec_};
64 : }
65 :
66 8622 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
67 : -> std::coroutine_handle<>
68 : {
69 8622 : token_ = env->stop_token;
70 8622 : cont_op_.cont.h = h;
71 8622 : auto& impl = t_.get();
72 : // Inline fast path: already expired and not in the heap
73 17222 : if (impl.heap_index_ == implementation::npos &&
74 17196 : (impl.expiry_ == (time_point::min)() ||
75 17218 : impl.expiry_ <= clock_type::now()))
76 : {
77 202 : ec_ = {};
78 202 : token_ = {}; // match normal path so await_resume
79 : // returns ec_, not a stale stop check
80 202 : auto d = env->executor;
81 202 : d.post(cont_op_.cont);
82 202 : return std::noop_coroutine();
83 : }
84 8420 : 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 20 : std::size_t cancel()
136 : {
137 20 : if (!get().might_have_pending_waits_)
138 12 : return 0;
139 8 : 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 38 : time_point expiry() const noexcept
148 : {
149 38 : 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 8622 : auto wait()
168 : {
169 8622 : 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 8625 : explicit io_timer(handle h) noexcept : io_object(std::move(h)) {}
180 :
181 : /// Move construct.
182 2 : 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 8680 : implementation& get() const noexcept
197 : {
198 8680 : return *static_cast<implementation*>(h_.get());
199 : }
200 : };
201 :
202 : } // namespace boost::corosio
203 :
204 : #endif
|