github.com/supragya/TendermintConnector@v0.0.0-20210619045051-113e32b84fb1/_deprecated_chains/cosmos/libs/common/repeat_timer.go (about) 1 package common 2 3 import ( 4 "sync" 5 "time" 6 ) 7 8 // Used by RepeatTimer the first time, 9 // and every time it's Reset() after Stop(). 10 type TickerMaker func(dur time.Duration) Ticker 11 12 // Ticker is a basic ticker interface. 13 type Ticker interface { 14 15 // Never changes, never closes. 16 Chan() <-chan time.Time 17 18 // Stopping a stopped Ticker will panic. 19 Stop() 20 } 21 22 //---------------------------------------- 23 // defaultTicker 24 25 var _ Ticker = (*defaultTicker)(nil) 26 27 type defaultTicker time.Ticker 28 29 func defaultTickerMaker(dur time.Duration) Ticker { 30 ticker := time.NewTicker(dur) 31 return (*defaultTicker)(ticker) 32 } 33 34 // Implements Ticker 35 func (t *defaultTicker) Chan() <-chan time.Time { 36 return t.C 37 } 38 39 // Implements Ticker 40 func (t *defaultTicker) Stop() { 41 ((*time.Ticker)(t)).Stop() 42 } 43 44 //---------------------------------------- 45 // LogicalTickerMaker 46 47 // Construct a TickerMaker that always uses `source`. 48 // It's useful for simulating a deterministic clock. 49 func NewLogicalTickerMaker(source chan time.Time) TickerMaker { 50 return func(dur time.Duration) Ticker { 51 return newLogicalTicker(source, dur) 52 } 53 } 54 55 type logicalTicker struct { 56 source <-chan time.Time 57 ch chan time.Time 58 quit chan struct{} 59 } 60 61 func newLogicalTicker(source <-chan time.Time, interval time.Duration) Ticker { 62 lt := &logicalTicker{ 63 source: source, 64 ch: make(chan time.Time), 65 quit: make(chan struct{}), 66 } 67 go lt.fireRoutine(interval) 68 return lt 69 } 70 71 // We need a goroutine to read times from t.source 72 // and fire on t.Chan() when `interval` has passed. 73 func (t *logicalTicker) fireRoutine(interval time.Duration) { 74 source := t.source 75 76 // Init `lasttime` 77 lasttime := time.Time{} 78 select { 79 case lasttime = <-source: 80 case <-t.quit: 81 return 82 } 83 // Init `lasttime` end 84 85 for { 86 select { 87 case newtime := <-source: 88 elapsed := newtime.Sub(lasttime) 89 if interval <= elapsed { 90 // Block for determinism until the ticker is stopped. 91 select { 92 case t.ch <- newtime: 93 case <-t.quit: 94 return 95 } 96 // Reset timeleft. 97 // Don't try to "catch up" by sending more. 98 // "Ticker adjusts the intervals or drops ticks to make up for 99 // slow receivers" - https://golang.org/pkg/time/#Ticker 100 lasttime = newtime 101 } 102 case <-t.quit: 103 return // done 104 } 105 } 106 } 107 108 // Implements Ticker 109 func (t *logicalTicker) Chan() <-chan time.Time { 110 return t.ch // immutable 111 } 112 113 // Implements Ticker 114 func (t *logicalTicker) Stop() { 115 close(t.quit) // it *should* panic when stopped twice. 116 } 117 118 //--------------------------------------------------------------------- 119 120 /* 121 RepeatTimer repeatedly sends a struct{}{} to `.Chan()` after each `dur` 122 period. (It's good for keeping connections alive.) 123 A RepeatTimer must be stopped, or it will keep a goroutine alive. 124 */ 125 type RepeatTimer struct { 126 name string 127 ch chan time.Time 128 tm TickerMaker 129 130 mtx sync.Mutex 131 dur time.Duration 132 ticker Ticker 133 quit chan struct{} 134 } 135 136 // NewRepeatTimer returns a RepeatTimer with a defaultTicker. 137 func NewRepeatTimer(name string, dur time.Duration) *RepeatTimer { 138 return NewRepeatTimerWithTickerMaker(name, dur, defaultTickerMaker) 139 } 140 141 // NewRepeatTimerWithTicker returns a RepeatTimer with the given ticker 142 // maker. 143 func NewRepeatTimerWithTickerMaker(name string, dur time.Duration, tm TickerMaker) *RepeatTimer { 144 var t = &RepeatTimer{ 145 name: name, 146 ch: make(chan time.Time), 147 tm: tm, 148 dur: dur, 149 ticker: nil, 150 quit: nil, 151 } 152 t.reset() 153 return t 154 } 155 156 // receive ticks on ch, send out on t.ch 157 func (t *RepeatTimer) fireRoutine(ch <-chan time.Time, quit <-chan struct{}) { 158 for { 159 select { 160 case tick := <-ch: 161 select { 162 case t.ch <- tick: 163 case <-quit: 164 return 165 } 166 case <-quit: // NOTE: `t.quit` races. 167 return 168 } 169 } 170 } 171 172 func (t *RepeatTimer) Chan() <-chan time.Time { 173 return t.ch 174 } 175 176 func (t *RepeatTimer) Stop() { 177 t.mtx.Lock() 178 defer t.mtx.Unlock() 179 180 t.stop() 181 } 182 183 // Wait the duration again before firing. 184 func (t *RepeatTimer) Reset() { 185 t.mtx.Lock() 186 defer t.mtx.Unlock() 187 188 t.reset() 189 } 190 191 //---------------------------------------- 192 // Misc. 193 194 // CONTRACT: (non-constructor) caller should hold t.mtx. 195 func (t *RepeatTimer) reset() { 196 if t.ticker != nil { 197 t.stop() 198 } 199 t.ticker = t.tm(t.dur) 200 t.quit = make(chan struct{}) 201 go t.fireRoutine(t.ticker.Chan(), t.quit) 202 } 203 204 // CONTRACT: caller should hold t.mtx. 205 func (t *RepeatTimer) stop() { 206 if t.ticker == nil { 207 /* 208 Similar to the case of closing channels twice: 209 https://groups.google.com/forum/#!topic/golang-nuts/rhxMiNmRAPk 210 Stopping a RepeatTimer twice implies that you do 211 not know whether you are done or not. 212 If you're calling stop on a stopped RepeatTimer, 213 you probably have race conditions. 214 */ 215 panic("Tried to stop a stopped RepeatTimer") 216 } 217 t.ticker.Stop() 218 t.ticker = nil 219 /* 220 From https://golang.org/pkg/time/#Ticker: 221 "Stop the ticker to release associated resources" 222 "After Stop, no more ticks will be sent" 223 So we shouldn't have to do the below. 224 225 select { 226 case <-t.ch: 227 // read off channel if there's anything there 228 default: 229 } 230 */ 231 close(t.quit) 232 }