github.com/DFWallet/tendermint-cosmos@v0.0.2/consensus/ticker.go (about) 1 package consensus 2 3 import ( 4 "time" 5 6 "github.com/DFWallet/tendermint-cosmos/libs/log" 7 "github.com/DFWallet/tendermint-cosmos/libs/service" 8 ) 9 10 var ( 11 tickTockBufferSize = 10 12 ) 13 14 // TimeoutTicker is a timer that schedules timeouts 15 // conditional on the height/round/step in the timeoutInfo. 16 // The timeoutInfo.Duration may be non-positive. 17 type TimeoutTicker interface { 18 Start() error 19 Stop() error 20 Chan() <-chan timeoutInfo // on which to receive a timeout 21 ScheduleTimeout(ti timeoutInfo) // reset the timer 22 23 SetLogger(log.Logger) 24 } 25 26 // timeoutTicker wraps time.Timer, 27 // scheduling timeouts only for greater height/round/step 28 // than what it's already seen. 29 // Timeouts are scheduled along the tickChan, 30 // and fired on the tockChan. 31 type timeoutTicker struct { 32 service.BaseService 33 34 timer *time.Timer 35 tickChan chan timeoutInfo // for scheduling timeouts 36 tockChan chan timeoutInfo // for notifying about them 37 } 38 39 // NewTimeoutTicker returns a new TimeoutTicker. 40 func NewTimeoutTicker() TimeoutTicker { 41 tt := &timeoutTicker{ 42 timer: time.NewTimer(0), 43 tickChan: make(chan timeoutInfo, tickTockBufferSize), 44 tockChan: make(chan timeoutInfo, tickTockBufferSize), 45 } 46 tt.BaseService = *service.NewBaseService(nil, "TimeoutTicker", tt) 47 tt.stopTimer() // don't want to fire until the first scheduled timeout 48 return tt 49 } 50 51 // OnStart implements service.Service. It starts the timeout routine. 52 func (t *timeoutTicker) OnStart() error { 53 54 go t.timeoutRoutine() 55 56 return nil 57 } 58 59 // OnStop implements service.Service. It stops the timeout routine. 60 func (t *timeoutTicker) OnStop() { 61 t.BaseService.OnStop() 62 t.stopTimer() 63 } 64 65 // Chan returns a channel on which timeouts are sent. 66 func (t *timeoutTicker) Chan() <-chan timeoutInfo { 67 return t.tockChan 68 } 69 70 // ScheduleTimeout schedules a new timeout by sending on the internal tickChan. 71 // The timeoutRoutine is always available to read from tickChan, so this won't block. 72 // The scheduling may fail if the timeoutRoutine has already scheduled a timeout for a later height/round/step. 73 func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) { 74 t.tickChan <- ti 75 } 76 77 //------------------------------------------------------------- 78 79 // stop the timer and drain if necessary 80 func (t *timeoutTicker) stopTimer() { 81 // Stop() returns false if it was already fired or was stopped 82 if !t.timer.Stop() { 83 select { 84 case <-t.timer.C: 85 default: 86 t.Logger.Debug("Timer already stopped") 87 } 88 } 89 } 90 91 // send on tickChan to start a new timer. 92 // timers are interupted and replaced by new ticks from later steps 93 // timeouts of 0 on the tickChan will be immediately relayed to the tockChan 94 func (t *timeoutTicker) timeoutRoutine() { 95 t.Logger.Debug("Starting timeout routine") 96 var ti timeoutInfo 97 for { 98 select { 99 case newti := <-t.tickChan: 100 t.Logger.Debug("Received tick", "old_ti", ti, "new_ti", newti) 101 102 // ignore tickers for old height/round/step 103 if newti.Height < ti.Height { 104 continue 105 } else if newti.Height == ti.Height { 106 if newti.Round < ti.Round { 107 continue 108 } else if newti.Round == ti.Round { 109 if ti.Step > 0 && newti.Step <= ti.Step { 110 continue 111 } 112 } 113 } 114 115 // stop the last timer 116 t.stopTimer() 117 118 // update timeoutInfo and reset timer 119 // NOTE time.Timer allows duration to be non-positive 120 ti = newti 121 t.timer.Reset(ti.Duration) 122 t.Logger.Debug("Scheduled timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) 123 case <-t.timer.C: 124 t.Logger.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) 125 // go routine here guarantees timeoutRoutine doesn't block. 126 // Determinism comes from playback in the receiveRoutine. 127 // We can eliminate it by merging the timeoutRoutine into receiveRoutine 128 // and managing the timeouts ourselves with a millisecond ticker 129 go func(toi timeoutInfo) { t.tockChan <- toi }(ti) 130 case <-t.Quit(): 131 return 132 } 133 } 134 }