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.

132 lines
3.8 KiB

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