You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
3.4 KiB

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