github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/common/fifoqueue/fifoqueue.go (about) 1 package fifoqueue 2 3 import ( 4 "fmt" 5 mathbits "math/bits" 6 "sync" 7 8 "github.com/ef-ds/deque" 9 ) 10 11 // CapacityUnlimited specifies the largest possible capacity for a FifoQueue. 12 // maximum value for platform-specific int: https://yourbasic.org/golang/max-min-int-uint/ 13 const CapacityUnlimited = 1<<(mathbits.UintSize-1) - 1 14 15 // FifoQueue implements a FIFO queue with max capacity and length observer. 16 // Elements that exceeds the queue's max capacity are silently dropped. 17 // By default, the theoretical capacity equals to the largest `int` value 18 // (platform dependent). Capacity can be set at construction time via the 19 // option `WithCapacity`. 20 // Each time the queue's length changes, the QueueLengthObserver is called 21 // with the new length. By default, the QueueLengthObserver is a NoOp. 22 // A single QueueLengthObserver can be set at construction time via the 23 // option `WithLengthObserver`. 24 // 25 // Caution: 26 // * the QueueLengthObserver must be non-blocking 27 type FifoQueue struct { 28 mu sync.RWMutex 29 queue deque.Deque 30 maxCapacity int 31 lengthObserver QueueLengthObserver 32 } 33 34 // ConstructorOptions are optional arguments for the `NewFifoQueue` 35 // constructor to specify properties of the FifoQueue. 36 type ConstructorOption func(*FifoQueue) error 37 38 // QueueLengthObserver is a optional callback that can be provided to 39 // the `NewFifoQueue` constructor (via `WithLengthObserver` option). 40 type QueueLengthObserver func(int) 41 42 // WithLengthObserver is a constructor option for NewFifoQueue. Each time the 43 // queue's length changes, the queue calls the provided callback with the new 44 // length. By default, the QueueLengthObserver is a NoOp. 45 // CAUTION: 46 // - QueueLengthObserver implementations must be non-blocking 47 // - The values published to queue length observer might be in different order 48 // than the actual length values at the time of insertion. This is a 49 // performance optimization, which allows to reduce the duration during 50 // which the queue is internally locked when inserting elements. 51 func WithLengthObserver(callback QueueLengthObserver) ConstructorOption { 52 return func(queue *FifoQueue) error { 53 if callback == nil { 54 return fmt.Errorf("nil is not a valid QueueLengthObserver") 55 } 56 queue.lengthObserver = callback 57 return nil 58 } 59 } 60 61 // WithLengthMetricObserver attaches a length observer which calls the given observe function. 62 // It can be used to concisely bind a metrics observer implementing module.MempoolMetrics to the queue. 63 func WithLengthMetricObserver(resource string, observe func(resource string, length uint)) ConstructorOption { 64 return WithLengthObserver(func(l int) { 65 observe(resource, uint(l)) 66 }) 67 } 68 69 // NewFifoQueue is the Constructor for FifoQueue 70 func NewFifoQueue(maxCapacity int, options ...ConstructorOption) (*FifoQueue, error) { 71 if maxCapacity < 1 { 72 return nil, fmt.Errorf("capacity for Fifo queue must be positive") 73 } 74 75 queue := &FifoQueue{ 76 maxCapacity: maxCapacity, 77 lengthObserver: func(int) { /* noop */ }, 78 } 79 for _, opt := range options { 80 err := opt(queue) 81 if err != nil { 82 return nil, fmt.Errorf("failed to apply constructor option to fifoqueue queue: %w", err) 83 } 84 } 85 return queue, nil 86 } 87 88 // Push appends the given value to the tail of the queue. 89 // If queue capacity is reached, the message is silently dropped. 90 func (q *FifoQueue) Push(element interface{}) bool { 91 length, pushed := q.push(element) 92 93 if pushed { 94 q.lengthObserver(length) 95 } 96 return pushed 97 } 98 99 func (q *FifoQueue) push(element interface{}) (int, bool) { 100 q.mu.Lock() 101 defer q.mu.Unlock() 102 103 length := q.queue.Len() 104 if length < q.maxCapacity { 105 q.queue.PushBack(element) 106 return q.queue.Len(), true 107 } 108 return length, false 109 } 110 111 // Front peeks message at the head of the queue (without removing the head). 112 func (q *FifoQueue) Head() (interface{}, bool) { 113 q.mu.RLock() 114 defer q.mu.RUnlock() 115 116 return q.queue.Front() 117 } 118 119 // Pop removes and returns the queue's head element. 120 // If the queue is empty, (nil, false) is returned. 121 func (q *FifoQueue) Pop() (interface{}, bool) { 122 event, length, ok := q.pop() 123 if !ok { 124 return nil, false 125 } 126 127 q.lengthObserver(length) 128 return event, true 129 } 130 131 func (q *FifoQueue) pop() (interface{}, int, bool) { 132 q.mu.Lock() 133 defer q.mu.Unlock() 134 135 event, ok := q.queue.PopFront() 136 return event, q.queue.Len(), ok 137 } 138 139 // Len returns the current length of the queue. 140 func (q *FifoQueue) Len() int { 141 q.mu.RLock() 142 defer q.mu.RUnlock() 143 144 return q.queue.Len() 145 }