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.

135 lines
4.0 KiB

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