xref: /aosp_15_r20/external/pigweed/pw_async2/public/pw_async2/simulated_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 "pw_async2/time_provider.h"
18 #include "pw_sync/interrupt_spin_lock.h"
19 
20 namespace pw::async2 {
21 
22 /// A simulated `TimeProvider` suitable for testing APIs which use `Timer`.
23 template <typename Clock>
24 class SimulatedTimeProvider final : public TimeProvider<Clock> {
25  public:
26   SimulatedTimeProvider(
27       typename Clock::time_point timestamp =
28           typename Clock::time_point(typename Clock::duration(0)))
now_(timestamp)29       : now_(timestamp) {}
30 
31   /// Advances the simulated time and runs any newly-expired timers.
AdvanceTime(typename Clock::duration duration)32   void AdvanceTime(typename Clock::duration duration) {
33     lock_.lock();
34     SetTimeUnlockAndRun(now_ + duration);
35   }
36 
37   /// Advances the simulated time until the next point at which a timer
38   /// would fire.
39   ///
40   /// Returns whether any timers were waiting to be run.
AdvanceUntilNextExpiration()41   bool AdvanceUntilNextExpiration() {
42     lock_.lock();
43     if (next_wake_time_ == std::nullopt) {
44       lock_.unlock();
45       return false;
46     }
47     SetTimeUnlockAndRun(*next_wake_time_);
48     return true;
49   }
50 
51   /// Modifies the simulated time and runs any newly-expired timers.
52   ///
53   /// WARNING: Use of this function with a timestamp older than the current
54   /// `now()` will violate the is_monotonic clock attribute. We don't like it
55   /// when time goes backwards!
SetTime(typename Clock::time_point new_now)56   void SetTime(typename Clock::time_point new_now) {
57     lock_.lock();
58     SetTimeUnlockAndRun(new_now);
59   }
60 
61   /// Explicitly run expired timers.
62   ///
63   /// Calls to this function are not usually necessary, as `AdvanceTime` and
64   /// `SetTime` will trigger expired timers to run. However, if a timer is set
65   /// for a time in the past and neither `AdvanceTime` nor `SetTime` are
66   /// subsequently invoked, the timer will not have a chance to run until
67   /// one of `AdvanceTime`, `SetTime`, or `RunExpiredTimers` has been called.
RunExpiredTimers()68   void RunExpiredTimers() { RunExpired(now()); }
69 
now()70   typename Clock::time_point now() final {
71     std::lock_guard lock(lock_);
72     return now_;
73   }
74 
NextExpiration()75   std::optional<typename Clock::time_point> NextExpiration() {
76     std::lock_guard lock(lock_);
77     return next_wake_time_;
78   }
79 
TimeUntilNextExpiration()80   std::optional<typename Clock::duration> TimeUntilNextExpiration() {
81     std::lock_guard lock(lock_);
82     if (next_wake_time_ == std::nullopt) {
83       return std::nullopt;
84     }
85     return *next_wake_time_ - now_;
86   }
87 
88  private:
SetTimeUnlockAndRun(typename Clock::time_point new_now)89   void SetTimeUnlockAndRun(typename Clock::time_point new_now)
90       PW_UNLOCK_FUNCTION(lock_) {
91     now_ = new_now;
92     if (new_now >= next_wake_time_) {
93       next_wake_time_ = std::nullopt;
94       lock_.unlock();
95       TimeProvider<Clock>::RunExpired(new_now);
96     } else {
97       lock_.unlock();
98     }
99   }
100 
DoInvokeAt(typename Clock::time_point wake_time)101   void DoInvokeAt(typename Clock::time_point wake_time) final {
102     std::lock_guard lock(lock_);
103     next_wake_time_ = wake_time;
104 
105     // We don't need to actually schedule anything here since calls to
106     // `RunExpired` are triggered directly by user calls to
107     // `AdvanceTime`/`SetTime`.
108     //
109     // Note: we cannot run the timer here even if it was in the past because
110     // `DoInvokeAt` is called under `TimeProvider`'s lock. Furthermore, we
111     // might be *inside* the current callback due to a nested invocation of
112     // `InvokeAfter`/`InvokeAt`.
113   }
114 
DoCancel()115   void DoCancel() final {
116     std::lock_guard lock(lock_);
117     next_wake_time_ = std::nullopt;
118 
119     // We don't need to do anything here since `RunExpired` itself is safe to
120     // call redundantly-- it will filter out extra invocations.
121   }
122 
123   mutable sync::InterruptSpinLock lock_;
124   typename Clock::time_point now_ PW_GUARDED_BY(lock_);
125   std::optional<typename Clock::time_point> next_wake_time_
126       PW_GUARDED_BY(lock_);
127 };
128 
129 }  // namespace pw::async2
130