github.com/okex/exchain@v1.8.0/libs/tendermint/consensus/ticker.go (about)

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