github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/common/mclock/simclock.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package mclock 19 20 import ( 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 []*simTimer 37 mu sync.RWMutex 38 cond *sync.Cond 39 lastId uint64 40 } 41 42 // simTimer implements Timer on the virtual clock. 43 type simTimer struct { 44 do func() 45 at AbsTime 46 id uint64 47 s *Simulated 48 } 49 50 // Run moves the clock by the given duration, executing all timers before that duration. 51 func (s *Simulated) Run(d time.Duration) { 52 s.mu.Lock() 53 s.init() 54 55 end := s.now + AbsTime(d) 56 var do []func() 57 for len(s.scheduled) > 0 { 58 ev := s.scheduled[0] 59 if ev.at > end { 60 break 61 } 62 s.now = ev.at 63 do = append(do, ev.do) 64 s.scheduled = s.scheduled[1:] 65 } 66 s.now = end 67 s.mu.Unlock() 68 69 for _, fn := range do { 70 fn() 71 } 72 } 73 74 // ActiveTimers returns the number of timers that haven't fired. 75 func (s *Simulated) ActiveTimers() int { 76 s.mu.RLock() 77 defer s.mu.RUnlock() 78 79 return len(s.scheduled) 80 } 81 82 // WaitForTimers waits until the clock has at least n scheduled timers. 83 func (s *Simulated) WaitForTimers(n int) { 84 s.mu.Lock() 85 defer s.mu.Unlock() 86 s.init() 87 88 for len(s.scheduled) < n { 89 s.cond.Wait() 90 } 91 } 92 93 // Now returns the current virtual time. 94 func (s *Simulated) Now() AbsTime { 95 s.mu.RLock() 96 defer s.mu.RUnlock() 97 98 return s.now 99 } 100 101 // Sleep blocks until the clock has advanced by d. 102 func (s *Simulated) Sleep(d time.Duration) { 103 <-s.After(d) 104 } 105 106 // After returns a channel which receives the current time after the clock 107 // has advanced by d. 108 func (s *Simulated) After(d time.Duration) <-chan time.Time { 109 after := make(chan time.Time, 1) 110 s.AfterFunc(d, func() { 111 after <- (time.Time{}).Add(time.Duration(s.now)) 112 }) 113 return after 114 } 115 116 // AfterFunc runs fn after the clock has advanced by d. Unlike with the system 117 // clock, fn runs on the goroutine that calls Run. 118 func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer { 119 s.mu.Lock() 120 defer s.mu.Unlock() 121 s.init() 122 123 at := s.now + AbsTime(d) 124 s.lastId++ 125 id := s.lastId 126 l, h := 0, len(s.scheduled) 127 ll := h 128 for l != h { 129 m := (l + h) / 2 130 if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) { 131 h = m 132 } else { 133 l = m + 1 134 } 135 } 136 ev := &simTimer{do: fn, at: at, s: s} 137 s.scheduled = append(s.scheduled, nil) 138 copy(s.scheduled[l+1:], s.scheduled[l:ll]) 139 s.scheduled[l] = ev 140 s.cond.Broadcast() 141 return ev 142 } 143 144 func (ev *simTimer) Stop() bool { 145 s := ev.s 146 s.mu.Lock() 147 defer s.mu.Unlock() 148 149 for i := 0; i < len(s.scheduled); i++ { 150 if s.scheduled[i] == ev { 151 s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...) 152 s.cond.Broadcast() 153 return true 154 } 155 } 156 return false 157 } 158 159 func (s *Simulated) init() { 160 if s.cond == nil { 161 s.cond = sync.NewCond(&s.mu) 162 } 163 }