1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #pragma once
16
17 #include <stddef.h>
18
19 #include <mutex>
20
21 #include "pw_async2/dispatcher.h"
22 #include "pw_chrono/virtual_clock.h"
23 #include "pw_containers/intrusive_list.h"
24 #include "pw_sync/interrupt_spin_lock.h"
25 #include "pw_sync/lock_annotations.h"
26 #include "pw_toolchain/no_destructor.h"
27
28 namespace pw::async2 {
29
30 namespace internal {
31
32 // A lock which guards `TimeProvider`'s linked list.
time_lock()33 inline pw::sync::InterruptSpinLock& time_lock() {
34 static pw::sync::InterruptSpinLock lock;
35 return lock;
36 }
37
38 // `Timer` objects must not outlive their `TimeProvider`.
39 void AssertTimeFutureObjectsAllGone(bool empty);
40
41 } // namespace internal
42
43 template <typename Clock>
44 class TimeFuture;
45
46 /// A factory for time and timers.
47 ///
48 /// This extends the `VirtualClock` interface with the ability to create async
49 /// timers.
50 ///
51 /// `TimeProvider` is designed to be dependency-injection friendly so that
52 /// code that uses time and timers is not bound to real wall-clock time.
53 /// This is particularly helpful for testing timing-sensitive code without
54 /// adding manual delays to tests (which often results in flakiness and
55 /// long-running tests).
56 ///
57 /// Note that `Timer` objects must not outlive the `TimeProvider` from which
58 /// they were created.
59 template <typename Clock>
60 class TimeProvider : public chrono::VirtualClock<Clock> {
61 public:
~TimeProvider()62 ~TimeProvider() override {
63 internal::AssertTimeFutureObjectsAllGone(futures_.empty());
64 }
65
66 typename Clock::time_point now() override = 0;
67
68 /// Queues the `callback` to be invoked after `delay`.
69 ///
70 /// This method is thread-safe and can be invoked from `callback` but may not
71 /// be interrupt-safe on all platforms.
WaitFor(typename Clock::duration delay)72 [[nodiscard]] TimeFuture<Clock> WaitFor(typename Clock::duration delay) {
73 /// The time_point is computed based on now() plus the specified duration
74 /// where a singular clock tick is added to handle partial ticks. This
75 /// ensures that a duration of at least 1 tick does not result in [0,1]
76 /// ticks and instead in [1,2] ticks.
77 return WaitUntil(now() + delay + typename Clock::duration(1));
78 }
79
80 /// Queues the `callback` to be invoked after `timestamp`.
81 ///
82 /// This method is thread-safe and can be invoked from `callback` but may not
83 /// be interrupt-safe on all platforms.
WaitUntil(typename Clock::time_point timestamp)84 [[nodiscard]] TimeFuture<Clock> WaitUntil(
85 typename Clock::time_point timestamp) {
86 return TimeFuture<Clock>(*this, timestamp);
87 }
88
89 protected:
90 /// Run all expired timers with the current (provided) `time_point`.
91 ///
92 /// This method should be invoked by subclasses when `DoInvokeAt`'s timer
93 /// expires.
94 void RunExpired(typename Clock::time_point now)
95 PW_LOCKS_EXCLUDED(internal::time_lock());
96
97 private:
98 friend class TimeFuture<Clock>;
99
100 /// Schedule `RunExpired` to be invoked at `time_point`.
101 /// Newer calls to `DoInvokeAt` supersede previous calls.
102 virtual void DoInvokeAt(typename Clock::time_point)
103 PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) = 0;
104
105 /// Optimistically cancels all pending `DoInvokeAt` requests.
106 virtual void DoCancel()
107 PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) = 0;
108
109 // Head of the waiting timers list.
110 IntrusiveList<TimeFuture<Clock>> futures_
111 PW_GUARDED_BY(internal::time_lock());
112 };
113
114 /// A timer which can asynchronously wait for time to pass.
115 ///
116 /// This timer uses a `TimeProvider` to control its execution and so can be
117 /// used with any `TimeProvider` with a compatible `Clock` type.
118 template <typename Clock>
119 class [[nodiscard]] TimeFuture
120 : public IntrusiveForwardList<TimeFuture<Clock>>::Item {
121 public:
TimeFuture()122 TimeFuture() : provider_(nullptr) {}
123 TimeFuture(const TimeFuture&) = delete;
124 TimeFuture& operator=(const TimeFuture&) = delete;
125
TimeFuture(TimeFuture && other)126 TimeFuture(TimeFuture&& other) : provider_(nullptr) {
127 *this = std::move(other);
128 }
129
130 TimeFuture& operator=(TimeFuture&& other) {
131 std::lock_guard lock(internal::time_lock());
132 UnlistLocked();
133
134 provider_ = other.provider_;
135 expiration_ = other.expiration_;
136
137 // Replace the entry of `other_` in the list.
138 if (!other.unlisted()) {
139 auto previous = provider_->futures_.before_begin();
140 while (&*std::next(previous) != &other) {
141 previous++;
142 }
143
144 // NOTE: this will leave `other` reporting (falsely) that it has expired.
145 // However, `other` should not be used post-`move`.
146 other.unlist(&*previous);
147 provider_->futures_.insert_after(previous, *this);
148 }
149
150 return *this;
151 }
152
153 /// Destructs `timer`.
154 ///
155 /// Destruction is thread-safe, but not necessarily interrupt-safe.
~TimeFuture()156 ~TimeFuture() { Unlist(); }
157
Pend(Context & cx)158 Poll<typename Clock::time_point> Pend(Context& cx)
159 PW_LOCKS_EXCLUDED(internal::time_lock()) {
160 std::lock_guard lock(internal::time_lock());
161 if (this->unlisted()) {
162 return Ready(expiration_);
163 }
164 // NOTE: this is done under the lock in order to ensure that `provider_` is
165 // not set to unlisted between it being initially read and `waker_` being
166 // set.
167 PW_ASYNC_STORE_WAKER(cx, waker_, "TimeFuture is waiting for a time_point");
168 return Pending();
169 }
170
171 /// Resets ``TimeFuture`` to expire at ``expiration``.
Reset(typename Clock::time_point expiration)172 void Reset(typename Clock::time_point expiration)
173 PW_LOCKS_EXCLUDED(internal::time_lock()) {
174 std::lock_guard lock(internal::time_lock());
175 UnlistLocked();
176 expiration_ = expiration;
177 EnlistLocked();
178 }
179
180 // Returns the provider associated with this timer.
181 //
182 // NOTE: this method must not be called before initializing the timer.
provider()183 TimeProvider<Clock>& provider() PW_NO_LOCK_SAFETY_ANALYSIS {
184 // A lock is not required because this value is only mutated in
185 // constructors.
186 return *provider_;
187 }
188
189 // Returns the provider associated with this timer.
190 //
191 // NOTE: this method must not be called before initializing the timer.
192 // NOTE: this method must not be called with other methods that modify
193 // the expiration time such as `Reset`.
expiration()194 typename Clock::time_point expiration() PW_NO_LOCK_SAFETY_ANALYSIS {
195 // A lock is not required because this is only mutated in ``Reset`` and
196 // constructors.
197 return expiration_;
198 }
199
200 private:
201 friend class TimeProvider<Clock>;
202
203 /// Constructs a `Timer` from a `TimeProvider` and a `time_point`.
TimeFuture(TimeProvider<Clock> & provider,typename Clock::time_point expiration)204 TimeFuture(TimeProvider<Clock>& provider,
205 typename Clock::time_point expiration)
206 : waker_(), provider_(&provider), expiration_(expiration) {
207 std::lock_guard lock(internal::time_lock());
208 EnlistLocked();
209 }
210
EnlistLocked()211 void EnlistLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) {
212 // Skip enlisting if the expiration of the timer is in the past.
213 // NOTE: this *does not* trigger a waker since `Poll` has not yet been
214 // invoked, so none has been registered.
215 if (provider_->now() >= expiration_) {
216 return;
217 }
218
219 if (provider_->futures_.empty() ||
220 provider_->futures_.front().expiration_ > expiration_) {
221 provider_->futures_.push_front(*this);
222 provider_->DoInvokeAt(expiration_);
223 return;
224 }
225 auto current = provider_->futures_.begin();
226 while (std::next(current) != provider_->futures_.end() &&
227 std::next(current)->expiration_ < expiration_) {
228 current++;
229 }
230 provider_->futures_.insert_after(current, *this);
231 }
232
Unlist()233 void Unlist() PW_LOCKS_EXCLUDED(internal::time_lock()) {
234 std::lock_guard lock(internal::time_lock());
235 UnlistLocked();
236 }
237
238 // Removes this timer from the `TimeProvider`'s list (if listed).
239 //
240 // If this timer was previously the `head` element of the `TimeProvider`'s
241 // list, the `TimeProvider` will be rescheduled to wake up based on the
242 // new `head`'s expiration time.
UnlistLocked()243 void UnlistLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) {
244 if (this->unlisted()) {
245 return;
246 }
247 if (&provider_->futures_.front() == this) {
248 provider_->futures_.pop_front();
249 if (provider_->futures_.empty()) {
250 provider_->DoCancel();
251 } else {
252 provider_->DoInvokeAt(provider_->futures_.front().expiration_);
253 }
254 return;
255 }
256
257 provider_->futures_.remove(*this);
258 }
259
260 Waker waker_;
261 // NOTE: the lock is only required when
262 // (1) modifying these fields or
263 // (2) reading these fields via the TimeProvider.
264 //
265 // Reading these fields when nonmodification is guaranteed (such as in an
266 // accessor like ``provider`` or ``expiration`` above) does not require
267 // holding the lock.
268 TimeProvider<Clock>* provider_ PW_GUARDED_BY(internal::time_lock());
269 typename Clock::time_point expiration_ PW_GUARDED_BY(internal::time_lock());
270 };
271
272 template <typename Clock>
RunExpired(typename Clock::time_point now)273 void TimeProvider<Clock>::RunExpired(typename Clock::time_point now) {
274 std::lock_guard lock(internal::time_lock());
275 while (!futures_.empty()) {
276 if (futures_.front().expiration_ > now) {
277 DoInvokeAt(futures_.front().expiration_);
278 return;
279 }
280 std::move(futures_.front().waker_).Wake();
281 futures_.pop_front();
282 }
283 }
284
285 } // namespace pw::async2
286