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  }