|
|
- package consensus
-
- import (
- "time"
-
- . "github.com/tendermint/go-common"
- )
-
- type TimeoutTicker interface {
- Start() (bool, error)
- Stop() bool
- Chan() <-chan timeoutInfo // on which to receive a timeout
- ScheduleTimeout(ti timeoutInfo) // reset the timer
- }
-
- type timeoutTicker struct {
- BaseService
-
- timer *time.Timer
- tickChan chan timeoutInfo
- tockChan chan timeoutInfo
- }
-
- func NewTimeoutTicker() TimeoutTicker {
- tt := &timeoutTicker{
- timer: time.NewTimer(0),
- tickChan: make(chan timeoutInfo, tickTockBufferSize),
- tockChan: make(chan timeoutInfo, tickTockBufferSize),
- }
- if !tt.timer.Stop() {
- <-tt.timer.C
- }
- tt.BaseService = *NewBaseService(log, "TimeoutTicker", tt)
- return tt
- }
-
- func (t *timeoutTicker) OnStart() error {
- t.BaseService.OnStart()
-
- go t.timeoutRoutine()
-
- return nil
- }
-
- func (t *timeoutTicker) OnStop() {
- t.BaseService.OnStop()
- }
-
- func (t *timeoutTicker) Chan() <-chan timeoutInfo {
- return t.tockChan
- }
-
- // The timeoutRoutine is alwaya available to read from tickChan (it won't block).
- // The scheduling may fail if the timeoutRoutine has already scheduled a timeout for a later height/round/step.
- func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) {
- t.tickChan <- ti
- }
-
- // send on tickChan to start a new timer.
- // timers are interupted and replaced by new ticks from later steps
- // timeouts of 0 on the tickChan will be immediately relayed to the tockChan
- func (t *timeoutTicker) timeoutRoutine() {
- log.Debug("Starting timeout routine")
- var ti timeoutInfo
- for {
- select {
- case newti := <-t.tickChan:
- log.Debug("Received tick", "old_ti", ti, "new_ti", newti)
-
- // ignore tickers for old height/round/step
- if newti.Height < ti.Height {
- continue
- } else if newti.Height == ti.Height {
- if newti.Round < ti.Round {
- continue
- } else if newti.Round == ti.Round {
- if ti.Step > 0 && newti.Step <= ti.Step {
- continue
- }
- }
- }
-
- ti = newti
-
- // if the newti has duration == 0, we relay to the tockChan immediately (no timeout)
- if ti.Duration == time.Duration(0) {
- go func(toi timeoutInfo) { t.tockChan <- toi }(ti)
- continue
- }
-
- log.Debug("Scheduling timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)
- if !t.timer.Stop() {
- select {
- case <-t.timer.C:
- default:
- }
- }
- t.timer.Reset(ti.Duration)
- case <-t.timer.C:
- log.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)
- // go routine here gaurantees timeoutRoutine doesn't block.
- // Determinism comes from playback in the receiveRoutine.
- // We can eliminate it by merging the timeoutRoutine into receiveRoutine
- // and managing the timeouts ourselves with a millisecond ticker
- go func(toi timeoutInfo) { t.tockChan <- toi }(ti)
- case <-t.Quit:
- return
- }
- }
- }
|