istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/loadbalancersim/timer/queue.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package timer 16 17 import ( 18 "container/heap" 19 "sync" 20 "time" 21 ) 22 23 type Queue struct { 24 heap timerHeap 25 mutex sync.Mutex 26 stopCh chan struct{} 27 resetTimerCh chan struct{} 28 stopping bool 29 timer *time.Timer 30 currentDeadline time.Time 31 } 32 33 func NewQueue() *Queue { 34 q := &Queue{ 35 heap: make(timerHeap, 0), 36 timer: time.NewTimer(1 * time.Minute), 37 stopCh: make(chan struct{}), 38 resetTimerCh: make(chan struct{}), 39 } 40 41 // Start the worker thread. 42 go func() { 43 for { 44 select { 45 case <-q.stopCh: 46 q.stopTimer() 47 return 48 case <-q.resetTimerCh: 49 q.resetTimer() 50 case <-q.timer.C: 51 q.onTimerExpired() 52 } 53 } 54 }() 55 56 return q 57 } 58 59 func (q *Queue) Len() int { 60 q.mutex.Lock() 61 defer q.mutex.Unlock() 62 return q.heap.Len() 63 } 64 65 func (q *Queue) Schedule(handler func(), deadline time.Time) { 66 // Add the timer to the heap. 67 q.mutex.Lock() 68 heap.Push(&q.heap, &entry{ 69 handler: handler, 70 deadline: deadline, 71 index: 0, 72 }) 73 q.mutex.Unlock() 74 75 // Request that the timer be reset. 76 q.resetTimerCh <- struct{}{} 77 } 78 79 func (q *Queue) ShutDown() { 80 close(q.stopCh) 81 } 82 83 func (q *Queue) stopTimer() { 84 q.mutex.Lock() 85 q.stopping = true 86 q.mutex.Unlock() 87 88 q.timer.Stop() 89 } 90 91 func (q *Queue) resetTimer() { 92 // Below is a separate function to limit the scope of the lock. 93 // We don't want to lock when we modify the timer in case it causes 94 // an immediate callback, which would reacquire the lock. 95 needReset, resetDuration := func() (bool, time.Duration) { 96 q.mutex.Lock() 97 defer q.mutex.Unlock() 98 99 if q.stopping { 100 // Ignore the event, since we're already shutting down. 101 return false, 0 102 } 103 104 e := q.heap.peek() 105 if e == nil || e.deadline.Equal(q.currentDeadline) { 106 // nothing to do. 107 return false, 0 108 } 109 110 q.currentDeadline = e.deadline 111 return true, time.Until(e.deadline) 112 }() 113 114 // Reset the timer. 115 if needReset { 116 q.timer.Reset(resetDuration) 117 } 118 } 119 120 func (q *Queue) onTimerExpired() { 121 // Collect all expired timers. 122 q.mutex.Lock() 123 handlers := q.heap.advanceTo(time.Now()) 124 q.mutex.Unlock() 125 126 // Call the expired timer handlers. 127 for _, h := range handlers { 128 h() 129 } 130 131 // Reset the timer based on the earliest deadline. 132 q.resetTimer() 133 } 134 135 type entry struct { 136 deadline time.Time 137 handler func() 138 index int 139 } 140 141 type timerHeap []*entry 142 143 func (h timerHeap) peek() *entry { 144 if h.Len() > 0 { 145 return h[0] 146 } 147 return nil 148 } 149 150 func (h *timerHeap) advanceTo(tnow time.Time) (out []func()) { 151 for { 152 if top := h.peek(); top != nil && !top.deadline.After(tnow) { 153 heap.Remove(h, top.index) 154 out = append(out, top.handler) 155 } else { 156 // There are no further expired timers. 157 return 158 } 159 } 160 } 161 162 func (h timerHeap) Len() int { 163 return len(h) 164 } 165 166 func (h timerHeap) Less(i, j int) bool { 167 return h[i].deadline.Before(h[j].deadline) 168 } 169 170 func (h timerHeap) Swap(i, j int) { 171 h[i], h[j] = h[j], h[i] 172 h[i].index = i 173 h[j].index = j 174 } 175 176 func (h *timerHeap) Push(x any) { 177 e := x.(*entry) 178 *h = append(*h, e) 179 e.index = len(*h) - 1 180 } 181 182 func (h *timerHeap) Pop() any { 183 n := h.Len() 184 e := (*h)[n-1] 185 *h = (*h)[:n-1] 186 e.index = -1 187 return e 188 }