github.com/coltonfike/e2c@v21.1.0+incompatible/common/mclock/simclock.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package mclock 18 19 import ( 20 "sync" 21 "time" 22 ) 23 24 // Simulated implements a virtual Clock for reproducible time-sensitive tests. It 25 // simulates a scheduler on a virtual timescale where actual processing takes zero time. 26 // 27 // The virtual clock doesn't advance on its own, call Run to advance it and execute timers. 28 // Since there is no way to influence the Go scheduler, testing timeout behaviour involving 29 // goroutines needs special care. A good way to test such timeouts is as follows: First 30 // perform the action that is supposed to time out. Ensure that the timer you want to test 31 // is created. Then run the clock until after the timeout. Finally observe the effect of 32 // the timeout using a channel or semaphore. 33 type Simulated struct { 34 now AbsTime 35 scheduled []*simTimer 36 mu sync.RWMutex 37 cond *sync.Cond 38 lastId uint64 39 } 40 41 // simTimer implements Timer on the virtual clock. 42 type simTimer struct { 43 do func() 44 at AbsTime 45 id uint64 46 s *Simulated 47 } 48 49 // Run moves the clock by the given duration, executing all timers before that duration. 50 func (s *Simulated) Run(d time.Duration) { 51 s.mu.Lock() 52 s.init() 53 54 end := s.now + AbsTime(d) 55 var do []func() 56 for len(s.scheduled) > 0 { 57 ev := s.scheduled[0] 58 if ev.at > end { 59 break 60 } 61 s.now = ev.at 62 do = append(do, ev.do) 63 s.scheduled = s.scheduled[1:] 64 } 65 s.now = end 66 s.mu.Unlock() 67 68 for _, fn := range do { 69 fn() 70 } 71 } 72 73 // ActiveTimers returns the number of timers that haven't fired. 74 func (s *Simulated) ActiveTimers() int { 75 s.mu.RLock() 76 defer s.mu.RUnlock() 77 78 return len(s.scheduled) 79 } 80 81 // WaitForTimers waits until the clock has at least n scheduled timers. 82 func (s *Simulated) WaitForTimers(n int) { 83 s.mu.Lock() 84 defer s.mu.Unlock() 85 s.init() 86 87 for len(s.scheduled) < n { 88 s.cond.Wait() 89 } 90 } 91 92 // Now returns the current virtual time. 93 func (s *Simulated) Now() AbsTime { 94 s.mu.RLock() 95 defer s.mu.RUnlock() 96 97 return s.now 98 } 99 100 // Sleep blocks until the clock has advanced by d. 101 func (s *Simulated) Sleep(d time.Duration) { 102 <-s.After(d) 103 } 104 105 // After returns a channel which receives the current time after the clock 106 // has advanced by d. 107 func (s *Simulated) After(d time.Duration) <-chan time.Time { 108 after := make(chan time.Time, 1) 109 s.AfterFunc(d, func() { 110 after <- (time.Time{}).Add(time.Duration(s.now)) 111 }) 112 return after 113 } 114 115 // AfterFunc runs fn after the clock has advanced by d. Unlike with the system 116 // clock, fn runs on the goroutine that calls Run. 117 func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer { 118 s.mu.Lock() 119 defer s.mu.Unlock() 120 s.init() 121 122 at := s.now + AbsTime(d) 123 s.lastId++ 124 id := s.lastId 125 l, h := 0, len(s.scheduled) 126 ll := h 127 for l != h { 128 m := (l + h) / 2 129 if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) { 130 h = m 131 } else { 132 l = m + 1 133 } 134 } 135 ev := &simTimer{do: fn, at: at, s: s} 136 s.scheduled = append(s.scheduled, nil) 137 copy(s.scheduled[l+1:], s.scheduled[l:ll]) 138 s.scheduled[l] = ev 139 s.cond.Broadcast() 140 return ev 141 } 142 143 func (ev *simTimer) Stop() bool { 144 s := ev.s 145 s.mu.Lock() 146 defer s.mu.Unlock() 147 148 for i := 0; i < len(s.scheduled); i++ { 149 if s.scheduled[i] == ev { 150 s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...) 151 s.cond.Broadcast() 152 return true 153 } 154 } 155 return false 156 } 157 158 func (s *Simulated) init() { 159 if s.cond == nil { 160 s.cond = sync.NewCond(&s.mu) 161 } 162 }