idletimer.go 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. package h2mux
  2. import (
  3. "math/rand"
  4. "sync"
  5. "time"
  6. )
  7. // IdleTimer is a type of Timer designed for managing heartbeats on an idle connection.
  8. // The timer ticks on an interval with added jitter to avoid accidental synchronisation
  9. // between two endpoints. It tracks the number of retries/ticks since the connection was
  10. // last marked active.
  11. //
  12. // The methods of IdleTimer must not be called while a goroutine is reading from C.
  13. type IdleTimer struct {
  14. // The channel on which ticks are delivered.
  15. C <-chan time.Time
  16. // A timer used to measure idle connection time. Reset after sending data.
  17. idleTimer *time.Timer
  18. // The maximum length of time a connection is idle before sending a ping.
  19. idleDuration time.Duration
  20. // A pseudorandom source used to add jitter to the idle duration.
  21. randomSource *rand.Rand
  22. // The maximum number of retries allowed.
  23. maxRetries uint64
  24. // The number of retries since the connection was last marked active.
  25. retries uint64
  26. // A lock to prevent race condition while checking retries
  27. stateLock sync.RWMutex
  28. }
  29. func NewIdleTimer(idleDuration time.Duration, maxRetries uint64) *IdleTimer {
  30. t := &IdleTimer{
  31. idleTimer: time.NewTimer(idleDuration),
  32. idleDuration: idleDuration,
  33. randomSource: rand.New(rand.NewSource(time.Now().Unix())),
  34. maxRetries: maxRetries,
  35. }
  36. t.C = t.idleTimer.C
  37. return t
  38. }
  39. // Retry should be called when retrying the idle timeout. If the maximum number of retries
  40. // has been met, returns false.
  41. // After calling this function and sending a heartbeat, call ResetTimer. Since sending the
  42. // heartbeat could be a blocking operation, we resetting the timer after the write completes
  43. // to avoid it expiring during the write.
  44. func (t *IdleTimer) Retry() bool {
  45. t.stateLock.Lock()
  46. defer t.stateLock.Unlock()
  47. if t.retries >= t.maxRetries {
  48. return false
  49. }
  50. t.retries++
  51. return true
  52. }
  53. func (t *IdleTimer) RetryCount() uint64 {
  54. t.stateLock.RLock()
  55. defer t.stateLock.RUnlock()
  56. return t.retries
  57. }
  58. // MarkActive resets the idle connection timer and suppresses any outstanding idle events.
  59. func (t *IdleTimer) MarkActive() {
  60. if !t.idleTimer.Stop() {
  61. // eat the timer event to prevent spurious pings
  62. <-t.idleTimer.C
  63. }
  64. t.stateLock.Lock()
  65. t.retries = 0
  66. t.stateLock.Unlock()
  67. t.ResetTimer()
  68. }
  69. // Reset the idle timer according to the configured duration, with some added jitter.
  70. func (t *IdleTimer) ResetTimer() {
  71. jitter := time.Duration(t.randomSource.Int63n(int64(t.idleDuration)))
  72. t.idleTimer.Reset(t.idleDuration + jitter)
  73. }