github.com/core-coin/go-core/v2@v2.1.9/common/mclock/simclock.go (about) 1 // Copyright 2018 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package mclock 18 19 import ( 20 "container/heap" 21 "sync" 22 "time" 23 ) 24 25 // Simulated implements a virtual Clock for reproducible time-sensitive tests. It 26 // simulates a scheduler on a virtual timescale where actual processing takes zero time. 27 // 28 // The virtual clock doesn't advance on its own, call Run to advance it and execute timers. 29 // Since there is no way to influence the Go scheduler, testing timeout behaviour involving 30 // goroutines needs special care. A good way to test such timeouts is as follows: First 31 // perform the action that is supposed to time out. Ensure that the timer you want to test 32 // is created. Then run the clock until after the timeout. Finally observe the effect of 33 // the timeout using a channel or semaphore. 34 type Simulated struct { 35 now AbsTime 36 scheduled simTimerHeap 37 mu sync.RWMutex 38 cond *sync.Cond 39 } 40 41 // simTimer implements ChanTimer on the virtual clock. 42 type simTimer struct { 43 at AbsTime 44 index int // position in s.scheduled 45 s *Simulated 46 do func() 47 ch <-chan AbsTime 48 } 49 50 func (s *Simulated) init() { 51 if s.cond == nil { 52 s.cond = sync.NewCond(&s.mu) 53 } 54 } 55 56 // Run moves the clock by the given duration, executing all timers before that duration. 57 func (s *Simulated) Run(d time.Duration) { 58 s.mu.Lock() 59 s.init() 60 61 end := s.now + AbsTime(d) 62 var do []func() 63 for len(s.scheduled) > 0 && s.scheduled[0].at <= end { 64 ev := heap.Pop(&s.scheduled).(*simTimer) 65 do = append(do, ev.do) 66 } 67 s.now = end 68 s.mu.Unlock() 69 70 for _, fn := range do { 71 fn() 72 } 73 } 74 75 // ActiveTimers returns the number of timers that haven't fired. 76 func (s *Simulated) ActiveTimers() int { 77 s.mu.RLock() 78 defer s.mu.RUnlock() 79 80 return len(s.scheduled) 81 } 82 83 // WaitForTimers waits until the clock has at least n scheduled timers. 84 func (s *Simulated) WaitForTimers(n int) { 85 s.mu.Lock() 86 defer s.mu.Unlock() 87 s.init() 88 89 for len(s.scheduled) < n { 90 s.cond.Wait() 91 } 92 } 93 94 // Now returns the current virtual time. 95 func (s *Simulated) Now() AbsTime { 96 s.mu.RLock() 97 defer s.mu.RUnlock() 98 99 return s.now 100 } 101 102 // Sleep blocks until the clock has advanced by d. 103 func (s *Simulated) Sleep(d time.Duration) { 104 <-s.After(d) 105 } 106 107 // NewTimer creates a timer which fires when the clock has advanced by d. 108 func (s *Simulated) NewTimer(d time.Duration) ChanTimer { 109 s.mu.Lock() 110 defer s.mu.Unlock() 111 112 ch := make(chan AbsTime, 1) 113 var timer *simTimer 114 timer = s.schedule(d, func() { ch <- timer.at }) 115 timer.ch = ch 116 return timer 117 } 118 119 // After returns a channel which receives the current time after the clock 120 // has advanced by d. 121 func (s *Simulated) After(d time.Duration) <-chan AbsTime { 122 return s.NewTimer(d).C() 123 } 124 125 // AfterFunc runs fn after the clock has advanced by d. Unlike with the system 126 // clock, fn runs on the goroutine that calls Run. 127 func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer { 128 s.mu.Lock() 129 defer s.mu.Unlock() 130 131 return s.schedule(d, fn) 132 } 133 134 func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer { 135 s.init() 136 137 at := s.now + AbsTime(d) 138 ev := &simTimer{do: fn, at: at, s: s} 139 heap.Push(&s.scheduled, ev) 140 s.cond.Broadcast() 141 return ev 142 } 143 144 func (ev *simTimer) Stop() bool { 145 ev.s.mu.Lock() 146 defer ev.s.mu.Unlock() 147 148 if ev.index < 0 { 149 return false 150 } 151 heap.Remove(&ev.s.scheduled, ev.index) 152 ev.s.cond.Broadcast() 153 ev.index = -1 154 return true 155 } 156 157 func (ev *simTimer) Reset(d time.Duration) { 158 if ev.ch == nil { 159 panic("mclock: Reset() on timer created by AfterFunc") 160 } 161 162 ev.s.mu.Lock() 163 defer ev.s.mu.Unlock() 164 ev.at = ev.s.now.Add(d) 165 if ev.index < 0 { 166 heap.Push(&ev.s.scheduled, ev) // already expired 167 } else { 168 heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule 169 } 170 ev.s.cond.Broadcast() 171 } 172 173 func (ev *simTimer) C() <-chan AbsTime { 174 if ev.ch == nil { 175 panic("mclock: C() on timer created by AfterFunc") 176 } 177 return ev.ch 178 } 179 180 type simTimerHeap []*simTimer 181 182 func (h *simTimerHeap) Len() int { 183 return len(*h) 184 } 185 186 func (h *simTimerHeap) Less(i, j int) bool { 187 return (*h)[i].at < (*h)[j].at 188 } 189 190 func (h *simTimerHeap) Swap(i, j int) { 191 (*h)[i], (*h)[j] = (*h)[j], (*h)[i] 192 (*h)[i].index = i 193 (*h)[j].index = j 194 } 195 196 func (h *simTimerHeap) Push(x interface{}) { 197 t := x.(*simTimer) 198 t.index = len(*h) 199 *h = append(*h, t) 200 } 201 202 func (h *simTimerHeap) Pop() interface{} { 203 end := len(*h) - 1 204 t := (*h)[end] 205 t.index = -1 206 (*h)[end] = nil 207 *h = (*h)[:end] 208 return t 209 }