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.

idletimer.go 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "fmt"
  6. "sync"
  7. "time"
  8. )
  9. const (
  10. // RegisterTimeout is how long clients have to register before we disconnect them
  11. RegisterTimeout = time.Minute
  12. // IdleTimeout is how long without traffic before a registered client is considered idle.
  13. IdleTimeout = time.Minute + time.Second*30
  14. // QuitTimeout is how long without traffic before an idle client is disconnected
  15. QuitTimeout = time.Minute
  16. )
  17. // client idleness state machine
  18. type TimerState uint
  19. const (
  20. TimerUnregistered TimerState = iota // client is unregistered
  21. TimerActive // client is actively sending commands
  22. TimerIdle // client is idle, we sent PING and are waiting for PONG
  23. )
  24. type IdleTimer struct {
  25. sync.Mutex
  26. // immutable after construction
  27. registerTimeout time.Duration
  28. idleTimeout time.Duration
  29. quitTimeout time.Duration
  30. // mutable
  31. client *Client
  32. state TimerState
  33. lastSeen time.Time
  34. }
  35. // NewIdleTimer sets up a new IdleTimer using constant timeouts.
  36. func NewIdleTimer(client *Client) *IdleTimer {
  37. it := IdleTimer{
  38. registerTimeout: RegisterTimeout,
  39. idleTimeout: IdleTimeout,
  40. quitTimeout: QuitTimeout,
  41. client: client,
  42. state: TimerUnregistered,
  43. }
  44. return &it
  45. }
  46. // Start starts counting idle time; if there is no activity from the client,
  47. // it will eventually be stopped.
  48. func (it *IdleTimer) Start() {
  49. it.Lock()
  50. it.state = TimerUnregistered
  51. it.lastSeen = time.Now()
  52. it.Unlock()
  53. go it.mainLoop()
  54. }
  55. func (it *IdleTimer) mainLoop() {
  56. for {
  57. it.Lock()
  58. client := it.client
  59. state := it.state
  60. lastSeen := it.lastSeen
  61. it.Unlock()
  62. if client == nil {
  63. return
  64. }
  65. idleTime := time.Now().Sub(lastSeen)
  66. var nextSleep time.Duration
  67. if state == TimerUnregistered {
  68. if client.Registered() {
  69. // transition to active, process new deadlines below
  70. state = TimerActive
  71. } else {
  72. nextSleep = it.registerTimeout - idleTime
  73. }
  74. } else if state == TimerIdle {
  75. if idleTime < it.quitTimeout {
  76. // new ping came in after we transitioned to TimerIdle,
  77. // transition back to active and process deadlines below
  78. state = TimerActive
  79. } else {
  80. nextSleep = 0
  81. }
  82. }
  83. if state == TimerActive {
  84. nextSleep = it.idleTimeout - idleTime
  85. if nextSleep <= 0 {
  86. state = TimerIdle
  87. client.Ping()
  88. // grant the client at least quitTimeout to respond
  89. nextSleep = it.quitTimeout
  90. }
  91. }
  92. if nextSleep <= 0 {
  93. // ran out of time, hang them up
  94. client.Quit(it.quitMessage(state))
  95. client.destroy()
  96. return
  97. }
  98. it.Lock()
  99. it.state = state
  100. it.Unlock()
  101. time.Sleep(nextSleep)
  102. }
  103. }
  104. // Touch registers activity (e.g., sending a command) from an client.
  105. func (it *IdleTimer) Touch() {
  106. it.Lock()
  107. client := it.client
  108. it.Unlock()
  109. // ignore touches for unregistered clients
  110. if client != nil && !client.Registered() {
  111. return
  112. }
  113. it.Lock()
  114. it.lastSeen = time.Now()
  115. it.Unlock()
  116. }
  117. // Stop stops counting idle time.
  118. func (it *IdleTimer) Stop() {
  119. it.Lock()
  120. defer it.Unlock()
  121. // no need to stop the goroutine, it'll clean itself up in a few minutes;
  122. // just ensure the Client object is collectable
  123. it.client = nil
  124. }
  125. func (it *IdleTimer) quitMessage(state TimerState) string {
  126. switch state {
  127. case TimerUnregistered:
  128. return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
  129. case TimerIdle:
  130. // how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
  131. return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
  132. default:
  133. // shouldn't happen
  134. return ""
  135. }
  136. }