xref: /aosp_15_r20/external/pigweed/pw_async2/public/pw_async2/time_provider.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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