2
0

timeout.go 4.3 KB

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