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  }