DEV Community

Yi Zhang
Yi Zhang

Posted on

How Tendermint Consensus Timeout Ticker works

In file "tendermint/consensus/ticker.go"



type TimeoutTicker interface {
        Start() error
        Stop() error
        Chan() <-chan timeoutInfo       // on which to receive a timeout
        ScheduleTimeout(ti timeoutInfo) // reset the timer

        SetLogger(log.Logger)
}

type timeoutTicker struct {
        service.BaseService

        timer    *time.Timer
        tickChan chan timeoutInfo // for scheduling timeouts
        tockChan chan timeoutInfo // for notifying about them
}

func NewTimeoutTicker() TimeoutTicker {
        tt := &timeoutTicker{
                timer:    time.NewTimer(0),
                tickChan: make(chan timeoutInfo, tickTockBufferSize),
                tockChan: make(chan timeoutInfo, tickTockBufferSize),
        }
        tt.BaseService = *service.NewBaseService(nil, "TimeoutTicker", tt)
        tt.stopTimer() // don't want to fire until the first scheduled timeout
        return tt
}

func (t *timeoutTicker) OnStart() error {
        go t.timeoutRoutine()
        return nil
}

func (t *timeoutTicker) OnStop() {
        t.BaseService.OnStop()
        t.stopTimer()
}

func (t *timeoutTicker) Chan() <-chan timeoutInfo {
        return t.tockChan
}

func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) {
        t.tickChan <- ti
}

func (t *timeoutTicker) stopTimer() {
        // Stop() returns false if it was already fired or was stopped
        if !t.timer.Stop() {
                select {
                case <-t.timer.C:
                default:
                        t.Logger.Debug("Timer already stopped")
                }
        }
}

func (t *timeoutTicker) timeoutRoutine() {
        t.Logger.Debug("Starting timeout routine")
        var ti timeoutInfo
        for {
                select {
                case newti := <-t.tickChan:
                        t.Logger.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
                                        }
                                }
                        }

                        // stop the last timer
                        t.stopTimer()

                        ti = newti
                        t.timer.Reset(ti.Duration)
                        t.Logger.Debug("Scheduled timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)
                case <-t.timer.C:
                        t.Logger.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)
                        go func(toi timeoutInfo) { t.tockChan <- toi }(ti)
                case <-t.Quit():
                        return
                }
        }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion:

timeouTicker is a service which we can start and stop. When started it runs a for loop to continuously and blockingly receive from 2 channels: "t.tickerChan" and "t.timer.C".

Basically the timeoutTiker allows caller to call "ScheduleTimeout(ti timeoutInfo)". The timeoutTicker will setup a timer to fire. When firing, the "timeoutInfo ti" will be available at the tockChan channel which caller can get by calling "timoutTicker.Chan()".

Top comments (0)