timeout_system.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package nebula
  2. import (
  3. "sync"
  4. "time"
  5. "github.com/slackhq/nebula/iputil"
  6. )
  7. // How many timer objects should be cached
  8. const systemTimerCacheMax = 50000
  9. type SystemTimerWheel struct {
  10. // Current tick
  11. current int
  12. // Cheat on finding the length of the wheel
  13. wheelLen int
  14. // Last time we ticked, since we are lazy ticking
  15. lastTick *time.Time
  16. // Durations of a tick and the entire wheel
  17. tickDuration time.Duration
  18. wheelDuration time.Duration
  19. // The actual wheel which is just a set of singly linked lists, head/tail pointers
  20. wheel []*SystemTimeoutList
  21. // Singly linked list of items that have timed out of the wheel
  22. expired *SystemTimeoutList
  23. // Item cache to avoid garbage collect
  24. itemCache *SystemTimeoutItem
  25. itemsCached int
  26. lock sync.Mutex
  27. }
  28. // SystemTimeoutList Represents a tick in the wheel
  29. type SystemTimeoutList struct {
  30. Head *SystemTimeoutItem
  31. Tail *SystemTimeoutItem
  32. }
  33. // SystemTimeoutItem Represents an item within a tick
  34. type SystemTimeoutItem struct {
  35. Item iputil.VpnIp
  36. Next *SystemTimeoutItem
  37. }
  38. // NewSystemTimerWheel Builds a timer wheel and identifies the tick duration and wheel duration from the provided values
  39. // Purge must be called once per entry to actually remove anything
  40. func NewSystemTimerWheel(min, max time.Duration) *SystemTimerWheel {
  41. //TODO provide an error
  42. //if min >= max {
  43. // return nil
  44. //}
  45. // Round down and add 2 so we can have the smallest # of ticks in the wheel and still account for a full
  46. // max duration, even if our current tick is at the maximum position and the next item to be added is at maximum
  47. // timeout
  48. wLen := int((max / min) + 2)
  49. tw := SystemTimerWheel{
  50. wheelLen: wLen,
  51. wheel: make([]*SystemTimeoutList, wLen),
  52. tickDuration: min,
  53. wheelDuration: max,
  54. expired: &SystemTimeoutList{},
  55. }
  56. for i := range tw.wheel {
  57. tw.wheel[i] = &SystemTimeoutList{}
  58. }
  59. return &tw
  60. }
  61. func (tw *SystemTimerWheel) Add(v iputil.VpnIp, timeout time.Duration) *SystemTimeoutItem {
  62. tw.lock.Lock()
  63. defer tw.lock.Unlock()
  64. // Check and see if we should progress the tick
  65. //tw.advance(time.Now())
  66. i := tw.findWheel(timeout)
  67. // Try to fetch off the cache
  68. ti := tw.itemCache
  69. if ti != nil {
  70. tw.itemCache = ti.Next
  71. ti.Next = nil
  72. tw.itemsCached--
  73. } else {
  74. ti = &SystemTimeoutItem{}
  75. }
  76. // Relink and return
  77. ti.Item = v
  78. ti.Next = tw.wheel[i].Head
  79. tw.wheel[i].Head = ti
  80. if tw.wheel[i].Tail == nil {
  81. tw.wheel[i].Tail = ti
  82. }
  83. return ti
  84. }
  85. func (tw *SystemTimerWheel) Purge() interface{} {
  86. tw.lock.Lock()
  87. defer tw.lock.Unlock()
  88. if tw.expired.Head == nil {
  89. return nil
  90. }
  91. ti := tw.expired.Head
  92. tw.expired.Head = ti.Next
  93. if tw.expired.Head == nil {
  94. tw.expired.Tail = nil
  95. }
  96. p := ti.Item
  97. // Clear out the items references
  98. ti.Item = 0
  99. ti.Next = nil
  100. // Maybe cache it for later
  101. if tw.itemsCached < systemTimerCacheMax {
  102. ti.Next = tw.itemCache
  103. tw.itemCache = ti
  104. tw.itemsCached++
  105. }
  106. return p
  107. }
  108. func (tw *SystemTimerWheel) findWheel(timeout time.Duration) (i int) {
  109. if timeout < tw.tickDuration {
  110. // Can't track anything below the set resolution
  111. timeout = tw.tickDuration
  112. } else if timeout > tw.wheelDuration {
  113. // We aren't handling timeouts greater than the wheels duration
  114. timeout = tw.wheelDuration
  115. }
  116. // Find the next highest, rounding up
  117. tick := int(((timeout - 1) / tw.tickDuration) + 1)
  118. // Add another tick since the current tick may almost be over then map it to the wheel from our
  119. // current position
  120. tick += tw.current + 1
  121. if tick >= tw.wheelLen {
  122. tick -= tw.wheelLen
  123. }
  124. return tick
  125. }
  126. func (tw *SystemTimerWheel) advance(now time.Time) {
  127. tw.lock.Lock()
  128. defer tw.lock.Unlock()
  129. if tw.lastTick == nil {
  130. tw.lastTick = &now
  131. }
  132. // We want to round down
  133. ticks := int(now.Sub(*tw.lastTick) / tw.tickDuration)
  134. //l.Infoln("Ticks: ", ticks)
  135. for i := 0; i < ticks; i++ {
  136. tw.current++
  137. //l.Infoln("Tick: ", tw.current)
  138. if tw.current >= tw.wheelLen {
  139. tw.current = 0
  140. }
  141. // We need to append the expired items as to not starve evicting the oldest ones
  142. if tw.expired.Tail == nil {
  143. tw.expired.Head = tw.wheel[tw.current].Head
  144. tw.expired.Tail = tw.wheel[tw.current].Tail
  145. } else {
  146. tw.expired.Tail.Next = tw.wheel[tw.current].Head
  147. if tw.wheel[tw.current].Tail != nil {
  148. tw.expired.Tail = tw.wheel[tw.current].Tail
  149. }
  150. }
  151. //l.Infoln("Head: ", tw.expired.Head, "Tail: ", tw.expired.Tail)
  152. tw.wheel[tw.current].Head = nil
  153. tw.wheel[tw.current].Tail = nil
  154. tw.lastTick = &now
  155. }
  156. }