timeout_system.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. // Represents a tick in the wheel
  29. type SystemTimeoutList struct {
  30. Head *SystemTimeoutItem
  31. Tail *SystemTimeoutItem
  32. }
  33. // Represents an item within a tick
  34. type SystemTimeoutItem struct {
  35. Item iputil.VpnIp
  36. Next *SystemTimeoutItem
  37. }
  38. // 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 1 so we can have the smallest # of ticks in the wheel and still account for a full
  46. // max duration
  47. wLen := int((max / min) + 1)
  48. tw := SystemTimerWheel{
  49. wheelLen: wLen,
  50. wheel: make([]*SystemTimeoutList, wLen),
  51. tickDuration: min,
  52. wheelDuration: max,
  53. expired: &SystemTimeoutList{},
  54. }
  55. for i := range tw.wheel {
  56. tw.wheel[i] = &SystemTimeoutList{}
  57. }
  58. return &tw
  59. }
  60. func (tw *SystemTimerWheel) Add(v iputil.VpnIp, timeout time.Duration) *SystemTimeoutItem {
  61. tw.lock.Lock()
  62. defer tw.lock.Unlock()
  63. // Check and see if we should progress the tick
  64. //tw.advance(time.Now())
  65. i := tw.findWheel(timeout)
  66. // Try to fetch off the cache
  67. ti := tw.itemCache
  68. if ti != nil {
  69. tw.itemCache = ti.Next
  70. ti.Next = nil
  71. tw.itemsCached--
  72. } else {
  73. ti = &SystemTimeoutItem{}
  74. }
  75. // Relink and return
  76. ti.Item = v
  77. ti.Next = tw.wheel[i].Head
  78. tw.wheel[i].Head = ti
  79. if tw.wheel[i].Tail == nil {
  80. tw.wheel[i].Tail = ti
  81. }
  82. return ti
  83. }
  84. func (tw *SystemTimerWheel) Purge() interface{} {
  85. tw.lock.Lock()
  86. defer tw.lock.Unlock()
  87. if tw.expired.Head == nil {
  88. return nil
  89. }
  90. ti := tw.expired.Head
  91. tw.expired.Head = ti.Next
  92. if tw.expired.Head == nil {
  93. tw.expired.Tail = nil
  94. }
  95. p := ti.Item
  96. // Clear out the items references
  97. ti.Item = 0
  98. ti.Next = nil
  99. // Maybe cache it for later
  100. if tw.itemsCached < systemTimerCacheMax {
  101. ti.Next = tw.itemCache
  102. tw.itemCache = ti
  103. tw.itemsCached++
  104. }
  105. return p
  106. }
  107. func (tw *SystemTimerWheel) findWheel(timeout time.Duration) (i int) {
  108. if timeout < tw.tickDuration {
  109. // Can't track anything below the set resolution
  110. timeout = tw.tickDuration
  111. } else if timeout > tw.wheelDuration {
  112. // We aren't handling timeouts greater than the wheels duration
  113. timeout = tw.wheelDuration
  114. }
  115. // Find the next highest, rounding up
  116. tick := int(((timeout - 1) / tw.tickDuration) + 1)
  117. // Add another tick since the current tick may almost be over then map it to the wheel from our
  118. // current position
  119. tick += tw.current + 1
  120. if tick >= tw.wheelLen {
  121. tick -= tw.wheelLen
  122. }
  123. return tick
  124. }
  125. func (tw *SystemTimerWheel) advance(now time.Time) {
  126. tw.lock.Lock()
  127. defer tw.lock.Unlock()
  128. if tw.lastTick == nil {
  129. tw.lastTick = &now
  130. }
  131. // We want to round down
  132. ticks := int(now.Sub(*tw.lastTick) / tw.tickDuration)
  133. //l.Infoln("Ticks: ", ticks)
  134. for i := 0; i < ticks; i++ {
  135. tw.current++
  136. //l.Infoln("Tick: ", tw.current)
  137. if tw.current >= tw.wheelLen {
  138. tw.current = 0
  139. }
  140. // We need to append the expired items as to not starve evicting the oldest ones
  141. if tw.expired.Tail == nil {
  142. tw.expired.Head = tw.wheel[tw.current].Head
  143. tw.expired.Tail = tw.wheel[tw.current].Tail
  144. } else {
  145. tw.expired.Tail.Next = tw.wheel[tw.current].Head
  146. if tw.wheel[tw.current].Tail != nil {
  147. tw.expired.Tail = tw.wheel[tw.current].Tail
  148. }
  149. }
  150. //l.Infoln("Head: ", tw.expired.Head, "Tail: ", tw.expired.Tail)
  151. tw.wheel[tw.current].Head = nil
  152. tw.wheel[tw.current].Tail = nil
  153. tw.lastTick = &now
  154. }
  155. }