github.com/prysmaticlabs/prysm@v1.4.4/shared/queue/priority_queue.go (about)

     1  // Copyright 2016 The Hashicorp vault Authors.
     2  // This file is part of the Hashicorp vault library.
     3  
     4  // Package queue provides Vault plugins with a Priority Queue. It can be used
     5  // as an in-memory list of queue.Item sorted by their priority, and offers
     6  // methods to find or remove items by their key. Internally it uses
     7  // container/heap; see Example Priority Queue:
     8  // https://golang.org/pkg/container/heap/#example__priorityQueue
     9  package queue
    10  
    11  import (
    12  	"container/heap"
    13  	"errors"
    14  	"sync"
    15  )
    16  
    17  // ErrEmpty is returned for queues with no items
    18  var ErrEmpty = errors.New("queue is empty")
    19  
    20  // ErrDuplicateItem is returned when the queue attmepts to push an item to a key that
    21  // already exists. The queue does not attempt to update, instead returns this
    22  // error. If an Item needs to be updated or replaced, pop the item first.
    23  var ErrDuplicateItem = errors.New("duplicate item")
    24  
    25  // New initializes the internal data structures and returns a new
    26  // PriorityQueue
    27  func New() *PriorityQueue {
    28  	pq := PriorityQueue{
    29  		data:    make(queue, 0),
    30  		dataMap: make(map[string]*Item),
    31  	}
    32  	heap.Init(&pq.data)
    33  	return &pq
    34  }
    35  
    36  // PriorityQueue facilitates queue of Items, providing Push, Pop, and
    37  // PopByKey convenience methods. The ordering (priority) is an int64 value
    38  // with the smallest value is the highest priority. PriorityQueue maintains both
    39  // an internal slice for the queue as well as a map of the same items with their
    40  // keys as the index. This enables users to find specific items by key. The map
    41  // must be kept in sync with the data slice.
    42  // See https://golang.org/pkg/container/heap/#example__priorityQueue
    43  type PriorityQueue struct {
    44  	// data is the internal structure that holds the queue, and is operated on by
    45  	// heap functions
    46  	data queue
    47  
    48  	// dataMap represents all the items in the queue, with unique indexes, used
    49  	// for finding specific items. dataMap is kept in sync with the data slice
    50  	dataMap map[string]*Item
    51  
    52  	// lock is a read/write mutex, and used to facilitate read/write locks on the
    53  	// data and dataMap fields
    54  	lock sync.RWMutex
    55  }
    56  
    57  // queue is the internal data structure used to satisfy heap.Interface. This
    58  // prevents users from calling Pop and Push heap methods directly
    59  type queue []*Item
    60  
    61  // Item is something managed in the priority queue
    62  type Item struct {
    63  	// Key is a unique string used to identify items in the internal data map
    64  	Key string
    65  	// Value is an unspecified type that implementations can use to store
    66  	// information
    67  	Value interface{}
    68  
    69  	// Priority determines ordering in the queue, with the lowest value being the
    70  	// highest priority
    71  	Priority int64
    72  
    73  	// index is an internal value used by the heap package, and should not be
    74  	// modified by any consumer of the priority queue
    75  	index int
    76  }
    77  
    78  // Len returns the count of items in the Priority Queue
    79  func (pq *PriorityQueue) Len() int {
    80  	pq.lock.RLock()
    81  	defer pq.lock.RUnlock()
    82  	return pq.data.Len()
    83  }
    84  
    85  // Pop pops the highest priority item from the queue. This is a
    86  // wrapper/convenience method that calls heap.Pop, so consumers do not need to
    87  // invoke heap functions directly
    88  func (pq *PriorityQueue) Pop() (*Item, error) {
    89  	if pq.Len() == 0 {
    90  		return nil, ErrEmpty
    91  	}
    92  
    93  	pq.lock.Lock()
    94  	defer pq.lock.Unlock()
    95  
    96  	item, ok := heap.Pop(&pq.data).(*Item)
    97  	if !ok {
    98  		return nil, errors.New("unknown type")
    99  	}
   100  	delete(pq.dataMap, item.Key)
   101  	return item, nil
   102  }
   103  
   104  // Push pushes an item on to the queue. This is a wrapper/convenience
   105  // method that calls heap.Push, so consumers do not need to invoke heap
   106  // functions directly. Items must have unique Keys, and Items in the queue
   107  // cannot be updated. To modify an Item, users must first remove it and re-push
   108  // it after modifications. Item does not get copied before pushing on to the queue,
   109  // it's up to the caller to copy the item.
   110  func (pq *PriorityQueue) Push(i *Item) error {
   111  	if i == nil || i.Key == "" {
   112  		return errors.New("error adding item: Item Key is required")
   113  	}
   114  
   115  	pq.lock.Lock()
   116  	defer pq.lock.Unlock()
   117  
   118  	if _, ok := pq.dataMap[i.Key]; ok {
   119  		return ErrDuplicateItem
   120  	}
   121  
   122  	pq.dataMap[i.Key] = i
   123  	heap.Push(&pq.data, i)
   124  	return nil
   125  }
   126  
   127  // PopByKey searches the queue for an item with the given key and removes it
   128  // from the queue if found. Returns nil if not found. This method must fix the
   129  // queue after removing any key.
   130  func (pq *PriorityQueue) PopByKey(key string) (*Item, error) {
   131  	pq.lock.Lock()
   132  	defer pq.lock.Unlock()
   133  
   134  	item, ok := pq.dataMap[key]
   135  	if !ok {
   136  		return nil, nil
   137  	}
   138  
   139  	// Remove the item the heap and delete it from the dataMap
   140  	itemRaw := heap.Remove(&pq.data, item.index)
   141  	delete(pq.dataMap, key)
   142  
   143  	if itemRaw != nil {
   144  		if i, ok := itemRaw.(*Item); ok {
   145  			return i, nil
   146  		}
   147  	}
   148  
   149  	return nil, nil
   150  }
   151  
   152  // Len returns the number of items in the queue data structure. Do not use this
   153  // method directly on the queue, use PriorityQueue.Len() instead.
   154  func (q queue) Len() int { return len(q) }
   155  
   156  // Less returns whether the Item with index i should sort before the Item with
   157  // index j in the queue. This method is used by the queue to determine priority
   158  // internally; the Item with the lower value wins. (priority zero is higher
   159  // priority than 1). The priority of Items with equal values is undetermined.
   160  func (q queue) Less(i, j int) bool {
   161  	return q[i].Priority < q[j].Priority
   162  }
   163  
   164  // Swap swaps things in-place; part of sort.Interface
   165  func (q queue) Swap(i, j int) {
   166  	q[i], q[j] = q[j], q[i]
   167  	q[i].index = i
   168  	q[j].index = j
   169  }
   170  
   171  // Push is used by heap.Interface to push items onto the heap. This method is
   172  // invoked by container/heap, and should not be used directly.
   173  // See: https://golang.org/pkg/container/heap/#Interface
   174  func (q *queue) Push(x interface{}) {
   175  	n := len(*q)
   176  	item, ok := x.(*Item)
   177  	if !ok {
   178  		return
   179  	}
   180  	item.index = n
   181  	*q = append(*q, item)
   182  }
   183  
   184  // Pop is used by heap.Interface to pop items off of the heap. This method is
   185  // invoked by container/heap, and should not be used directly.
   186  // See: https://golang.org/pkg/container/heap/#Interface
   187  func (q *queue) Pop() interface{} {
   188  	old := *q
   189  	n := len(old)
   190  	item := old[n-1]
   191  	old[n-1] = nil  // avoid memory leak
   192  	item.index = -1 // for safety
   193  	*q = old[0 : n-1]
   194  	return item
   195  }