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