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.

110 lines
2.8 KiB

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