github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/queue/queue.go (about) 1 /* 2 Copyright 2021 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package queue contains methods and structures for manipulating and persisting 18 // data that should be repeatedly processed in a prioritized (but changing) order. 19 // 20 // Esepcially useful for combining with systems like pubsub to receive events 21 // that change the order of queue processing. 22 package queue 23 24 import ( 25 "container/heap" 26 "context" 27 "errors" 28 "fmt" 29 "sync" 30 "time" 31 32 "github.com/sirupsen/logrus" 33 ) 34 35 // Queue can send names to receivers at a specific frequency. 36 // 37 // Also contains the ability to modify the next time to send names. 38 // First call must be to Init(). 39 // Exported methods are safe to call concurrently. 40 type Queue struct { 41 queue priorityQueue 42 items map[string]*item 43 lock sync.RWMutex 44 signal chan struct{} 45 log logrus.FieldLogger 46 } 47 48 // Init (or reinit) the queue with the specified groups, which should be updated at frequency. 49 func (q *Queue) Init(log logrus.FieldLogger, names []string, when time.Time) { 50 n := len(names) 51 found := make(map[string]bool, n) 52 53 q.lock.Lock() 54 defer q.lock.Unlock() 55 defer q.rouse() 56 57 if q.signal == nil { 58 q.signal = make(chan struct{}) 59 } 60 61 if q.items == nil { 62 q.items = make(map[string]*item, n) 63 } 64 if q.queue == nil { 65 q.queue = make(priorityQueue, 0, n) 66 } 67 items := q.items 68 69 q.log = log 70 for _, name := range names { 71 found[name] = true 72 if _, ok := items[name]; ok { 73 continue 74 } 75 it := &item{ 76 name: name, 77 when: when, 78 index: len(q.queue), 79 } 80 heap.Push(&q.queue, it) 81 items[name] = it 82 log.WithFields(logrus.Fields{ 83 "when": when, 84 "name": name, 85 }).Info("Adding name to queue") 86 } 87 88 for name, it := range items { 89 if found[name] { 90 continue 91 } 92 log.WithField("name", name).Info("Removing name from queue") 93 heap.Remove(&q.queue, it.index) 94 delete(q.items, name) 95 } 96 } 97 98 // FixAll will fix multiple groups inside a single critical section. 99 // 100 // If later is set then it will move out the next update time, otherwise 101 // it will only reduce it. 102 func (q *Queue) FixAll(whens map[string]time.Time, later bool) error { 103 q.lock.Lock() 104 defer q.lock.Unlock() 105 var missing []string 106 defer q.rouse() 107 108 reduced := map[string]time.Time{} 109 fixed := map[string]time.Time{} 110 111 for name, when := range whens { 112 it, ok := q.items[name] 113 if !ok { 114 missing = append(missing, name) 115 continue 116 } 117 if when.Before(it.when) { 118 reduced[name] = when 119 } else if later && !when.Equal(it.when) { 120 fixed[name] = when 121 } else { 122 continue 123 } 124 it.when = when 125 } 126 log := q.log 127 var any bool 128 if n := len(reduced); n > 0 { 129 log = log.WithField("reduced", n) 130 any = true 131 } 132 if n := len(fixed); n > 0 { 133 log = log.WithField("fixed", n) 134 any = true 135 } 136 heap.Init(&q.queue) 137 if len(missing) > 0 { 138 return fmt.Errorf("not found: %v", missing) 139 } 140 if any { 141 log.Info("Fixed all names") 142 } 143 return nil 144 } 145 146 // Fix the next time to send the group to receivers. 147 // 148 // If later is set then it will move out the next update time, otherwise 149 // it will only reduce it. 150 func (q *Queue) Fix(name string, when time.Time, later bool) error { 151 q.lock.Lock() 152 defer q.lock.Unlock() 153 defer q.rouse() 154 155 it, ok := q.items[name] 156 if !ok { 157 return errors.New("not found") 158 } 159 log := q.log.WithFields(logrus.Fields{ 160 "group": name, 161 "when": when, 162 }) 163 if when.Before(it.when) { 164 log = log.WithField("reduced minutes", it.when.Sub(when).Round(time.Second).Minutes()) 165 } else if later && !when.Equal(it.when) { 166 log = log.WithField("delayed minutes", when.Sub(it.when).Round(time.Second).Minutes()) 167 } else { 168 return nil 169 } 170 it.when = when 171 heap.Fix(&q.queue, it.index) 172 log.Info("Fixed names") 173 return nil 174 } 175 176 // Current status for each item in the queue. 177 func (q *Queue) Current() map[string]time.Time { 178 currently := make(map[string]time.Time, len(q.queue)) 179 q.lock.RLock() 180 defer q.lock.RUnlock() 181 for _, item := range q.queue { 182 currently[item.name] = item.when 183 } 184 return currently 185 } 186 187 // Status of the queue: depth, next item and when the next item is ready. 188 func (q *Queue) Status() (int, *string, time.Time) { 189 q.lock.RLock() 190 defer q.lock.RUnlock() 191 var who *string 192 var when time.Time 193 if it := q.queue.peek(); it != nil { 194 who = &it.name 195 when = it.when 196 } 197 return len(q.queue), who, when 198 } 199 200 func (q *Queue) rouse() { 201 select { 202 case q.signal <- struct{}{}: // wake up early 203 default: // not sleeping 204 } 205 } 206 207 func (q *Queue) sleep(d time.Duration) { 208 log := q.log.WithFields(logrus.Fields{ 209 "seconds": d.Round(100 * time.Millisecond).Seconds(), 210 }) 211 if d > 5*time.Second { 212 log.Info("Sleeping...") 213 } else { 214 log.Debug("Sleeping...") 215 } 216 sleep := time.NewTimer(d) 217 defer sleep.Stop() 218 start := time.Now() 219 select { 220 case <-q.signal: 221 dur := time.Now().Sub(start) 222 log := log.WithField("after", dur.Round(time.Millisecond)) 223 switch { 224 case dur > 10*time.Second: 225 log.Info("Roused") 226 case dur > time.Second: 227 log.Debug("Roused") 228 default: 229 log.Trace("Roused") 230 } 231 case <-sleep.C: 232 } 233 } 234 235 // Send test groups to receivers until the context expires. 236 // 237 // Pops items off the queue when frequency is zero. 238 // Otherwise reschedules the item after the specified frequency has elapsed. 239 func (q *Queue) Send(ctx context.Context, receivers chan<- string, frequency time.Duration) error { 240 var next func() (*string, time.Time) 241 if frequency == 0 { 242 next = func() (*string, time.Time) { 243 if len(q.queue) == 0 { 244 return nil, time.Time{} 245 } 246 it := heap.Pop(&q.queue).(*item) 247 return &it.name, it.when 248 } 249 } else { 250 next = func() (*string, time.Time) { 251 it := q.queue.peek() 252 if it == nil { 253 return nil, time.Time{} 254 } 255 when := it.when 256 it.when = time.Now().Add(frequency) 257 heap.Fix(&q.queue, it.index) 258 return &it.name, when 259 } 260 } 261 262 for { 263 if err := ctx.Err(); err != nil { 264 return err 265 } 266 q.lock.Lock() 267 who, when := next() 268 q.lock.Unlock() 269 270 if who == nil { 271 if frequency == 0 { 272 return nil 273 } 274 q.sleep(time.Second) 275 continue 276 } 277 278 if dur := time.Until(when); dur > 0 { 279 q.sleep(dur) 280 } 281 select { 282 case receivers <- *who: 283 case <-ctx.Done(): 284 return ctx.Err() 285 } 286 } 287 } 288 289 type priorityQueue []*item 290 291 func (pq priorityQueue) Len() int { return len(pq) } 292 func (pq priorityQueue) Less(i, j int) bool { 293 return pq[i].when.Before(pq[j].when) 294 } 295 func (pq priorityQueue) Swap(i, j int) { 296 pq[i], pq[j] = pq[j], pq[i] 297 pq[i].index = i 298 pq[j].index = j 299 } 300 301 func (pq *priorityQueue) Push(something interface{}) { 302 it := something.(*item) 303 it.index = len(*pq) 304 *pq = append(*pq, it) 305 } 306 307 func (pq *priorityQueue) Pop() interface{} { 308 old := *pq 309 n := len(old) 310 it := old[n-1] 311 it.index = -1 312 old[n-1] = nil 313 *pq = old[0 : n-1] 314 return it 315 } 316 317 func (pq priorityQueue) peek() *item { 318 n := len(pq) 319 if n == 0 { 320 return nil 321 } 322 return pq[0] 323 } 324 325 type item struct { 326 name string 327 when time.Time 328 index int 329 }