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.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "time"
  6. )
  7. // BrbTimer is a timer on the client as a whole (not an individual session) for implementing
  8. // the BRB command and related functionality (where a client can remain online without
  9. // having any connected sessions).
  10. type BrbState uint
  11. const (
  12. // BrbDisabled is the default state; the client will be disconnected if it has no sessions
  13. BrbDisabled BrbState = iota
  14. // BrbEnabled allows the client to remain online without sessions; if a timeout is
  15. // reached, it will be removed
  16. BrbEnabled
  17. // BrbDead is the state of a client after its timeout has expired; it will be removed
  18. // and therefore new sessions cannot be attached to it
  19. BrbDead
  20. )
  21. type BrbTimer struct {
  22. // XXX we use client.stateMutex for synchronization, so we can atomically test
  23. // conditions that use both brbTimer.state and client.sessions. This code
  24. // is tightly coupled with the rest of Client.
  25. client *Client
  26. state BrbState
  27. brbAt time.Time
  28. duration time.Duration
  29. timer *time.Timer
  30. }
  31. func (bt *BrbTimer) Initialize(client *Client) {
  32. bt.client = client
  33. }
  34. // attempts to enable BRB for a client, returns whether it succeeded
  35. func (bt *BrbTimer) Enable() (success bool, duration time.Duration) {
  36. // TODO make this configurable
  37. duration = ResumeableTotalTimeout
  38. bt.client.stateMutex.Lock()
  39. defer bt.client.stateMutex.Unlock()
  40. if !bt.client.registered || bt.client.alwaysOn || bt.client.resumeID == "" {
  41. return
  42. }
  43. switch bt.state {
  44. case BrbDisabled, BrbEnabled:
  45. bt.state = BrbEnabled
  46. bt.duration = duration
  47. bt.resetTimeout()
  48. // only track the earliest BRB, if multiple sessions are BRB'ing at once
  49. // TODO(#524) this is inaccurate in case of an auto-BRB
  50. if bt.brbAt.IsZero() {
  51. bt.brbAt = time.Now().UTC()
  52. }
  53. success = true
  54. default:
  55. // BrbDead
  56. success = false
  57. }
  58. return
  59. }
  60. // turns off BRB for a client and stops the timer; used on resume and during
  61. // client teardown
  62. func (bt *BrbTimer) Disable() (brbAt time.Time) {
  63. bt.client.stateMutex.Lock()
  64. defer bt.client.stateMutex.Unlock()
  65. if bt.state == BrbEnabled {
  66. bt.state = BrbDisabled
  67. brbAt = bt.brbAt
  68. bt.brbAt = time.Time{}
  69. }
  70. bt.resetTimeout()
  71. return
  72. }
  73. func (bt *BrbTimer) resetTimeout() {
  74. if bt.timer != nil {
  75. bt.timer.Stop()
  76. }
  77. if bt.state != BrbEnabled {
  78. return
  79. }
  80. if bt.timer == nil {
  81. bt.timer = time.AfterFunc(bt.duration, bt.processTimeout)
  82. } else {
  83. bt.timer.Reset(bt.duration)
  84. }
  85. }
  86. func (bt *BrbTimer) processTimeout() {
  87. dead := false
  88. defer func() {
  89. if dead {
  90. bt.client.Quit(bt.client.AwayMessage(), nil)
  91. bt.client.destroy(nil)
  92. }
  93. }()
  94. bt.client.stateMutex.Lock()
  95. defer bt.client.stateMutex.Unlock()
  96. if bt.client.alwaysOn {
  97. return
  98. }
  99. switch bt.state {
  100. case BrbDisabled, BrbEnabled:
  101. if len(bt.client.sessions) == 0 {
  102. // client never returned, quit them
  103. bt.state = BrbDead
  104. dead = true
  105. } else {
  106. // client resumed, reattached, or has another active session
  107. bt.state = BrbDisabled
  108. bt.brbAt = time.Time{}
  109. }
  110. case BrbDead:
  111. dead = true // shouldn't be possible but whatever
  112. }
  113. bt.resetTimeout()
  114. }