github.com/blend/go-sdk@v1.20220411.3/cache/lru_queue.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package cache
     9  
    10  import (
    11  	"sort"
    12  )
    13  
    14  var (
    15  	_ LRU = (*LRUQueue)(nil)
    16  )
    17  
    18  // NewLRUQueue creates a new, empty, LRUQueue.
    19  func NewLRUQueue() *LRUQueue {
    20  	return &LRUQueue{
    21  		array: make([]*Value, ringBufferDefaultCapacity),
    22  	}
    23  }
    24  
    25  // LRUQueue is a fifo buffer that is backed by a pre-allocated array, instead of allocating
    26  // a whole new node object for each element (which saves GC churn).
    27  // Enqueue can be O(n), Dequeue can be O(1).
    28  type LRUQueue struct {
    29  	array []*Value
    30  	head  int
    31  	tail  int
    32  	size  int
    33  }
    34  
    35  // Len returns the length of the ring buffer (as it is currently populated).
    36  // Actual memory footprint may be different.
    37  func (lru *LRUQueue) Len() (len int) {
    38  	return lru.size
    39  }
    40  
    41  // Capacity returns the total size of the ring bufffer, including empty elements.
    42  func (lru *LRUQueue) Capacity() int {
    43  	return len(lru.array)
    44  }
    45  
    46  // Clear removes all objects from the LRUQueue.
    47  func (lru *LRUQueue) Clear() {
    48  	if lru.head < lru.tail {
    49  		arrayClear(lru.array, lru.head, lru.size)
    50  	} else {
    51  		arrayClear(lru.array, lru.head, len(lru.array)-lru.head)
    52  		arrayClear(lru.array, 0, lru.tail)
    53  	}
    54  	lru.head = 0
    55  	lru.tail = 0
    56  	lru.size = 0
    57  }
    58  
    59  // Push adds an element to the "back" of the LRUQueue.
    60  func (lru *LRUQueue) Push(object *Value) {
    61  	if lru.size == len(lru.array) { // if we're out of room
    62  		lru.setCapacity(lru.growCapacity())
    63  	}
    64  	lru.array[lru.tail] = object
    65  	lru.tail = (lru.tail + 1) % len(lru.array)
    66  	lru.size++
    67  }
    68  
    69  // Pop removes the first (oldest) element from the LRUQueue.
    70  func (lru *LRUQueue) Pop() *Value {
    71  	if lru.size == 0 {
    72  		return nil
    73  	}
    74  
    75  	removed := lru.array[lru.head]
    76  	lru.head = (lru.head + 1) % len(lru.array)
    77  	lru.size--
    78  	return removed
    79  }
    80  
    81  // Peek returns but does not remove the first element.
    82  func (lru *LRUQueue) Peek() *Value {
    83  	if lru.size == 0 {
    84  		return nil
    85  	}
    86  	return lru.array[lru.head]
    87  }
    88  
    89  // PeekBack returns but does not remove the last element.
    90  func (lru *LRUQueue) PeekBack() *Value {
    91  	if lru.size == 0 {
    92  		return nil
    93  	}
    94  	if lru.tail == 0 {
    95  		return lru.array[len(lru.array)-1]
    96  	}
    97  	return lru.array[lru.tail-1]
    98  }
    99  
   100  // Fix updates the queue given an update to a specific value.
   101  func (lru *LRUQueue) Fix(value *Value) {
   102  	if lru.size == 0 {
   103  		return
   104  	}
   105  	if value == nil {
   106  		panic("lru queue; value is nil")
   107  	}
   108  
   109  	values := make([]*Value, lru.size)
   110  	var index int
   111  	var didUpdate bool
   112  	lru.Each(func(v *Value) bool {
   113  		if v.Key == value.Key {
   114  			didUpdate = v.Expires != value.Expires
   115  			values[index] = value
   116  		} else {
   117  			values[index] = v
   118  		}
   119  		index++
   120  		return true
   121  	})
   122  	if didUpdate {
   123  		sort.Sort(LRUHeapValues(values))
   124  	}
   125  	lru.array = make([]*Value, len(lru.array))
   126  	copy(lru.array, values)
   127  	lru.head = 0
   128  	lru.tail = lru.size
   129  }
   130  
   131  // Remove removes an item from the queue by its key.
   132  func (lru *LRUQueue) Remove(key interface{}) {
   133  	if lru.size == 0 {
   134  		return
   135  	}
   136  	if key == nil {
   137  		panic("lru queue; key is nil")
   138  	}
   139  
   140  	size := lru.size
   141  
   142  	values := make([]*Value, size-1)
   143  	var cursor int
   144  	for x := 0; x < size; x++ {
   145  		head := lru.Pop()
   146  		if head.Key != key {
   147  			values[cursor] = head
   148  			cursor++
   149  		}
   150  	}
   151  	for x := 0; x < len(values); x++ {
   152  		lru.Push(values[x])
   153  	}
   154  }
   155  
   156  // Each iterates through the queue and calls the consumer for each element of the queue.
   157  func (lru *LRUQueue) Each(consumer func(*Value) bool) {
   158  	if lru.size == 0 {
   159  		return
   160  	}
   161  
   162  	if lru.head < lru.tail {
   163  		for cursor := lru.head; cursor < lru.tail; cursor++ {
   164  			if !consumer(lru.array[cursor]) {
   165  				return
   166  			}
   167  		}
   168  	} else {
   169  		for cursor := lru.head; cursor < len(lru.array); cursor++ {
   170  			if !consumer(lru.array[cursor]) {
   171  				return
   172  			}
   173  		}
   174  		for cursor := 0; cursor < lru.tail; cursor++ {
   175  			if !consumer(lru.array[cursor]) {
   176  				return
   177  			}
   178  		}
   179  	}
   180  }
   181  
   182  // Consume calls the consumer for each element in the buffer. If the handler returns true,
   183  // the element is popped and the handler is called on the next value.
   184  func (lru *LRUQueue) Consume(consumer func(*Value) bool) {
   185  	if lru.size == 0 {
   186  		return
   187  	}
   188  
   189  	for i := 0; i < lru.size; i++ {
   190  		if !consumer(lru.Peek()) {
   191  			return
   192  		}
   193  		lru.Pop()
   194  	}
   195  }
   196  
   197  // Reset removes all elements from the heap, leaving an empty heap.
   198  func (lru *LRUQueue) Reset() {
   199  	lru.array = make([]*Value, ringBufferDefaultCapacity)
   200  	lru.head = 0
   201  	lru.tail = 0
   202  	lru.size = 0
   203  }
   204  
   205  //
   206  // util / helpers
   207  //
   208  
   209  // TrimExcess trims the excess space in the ringbuffer.
   210  func (lru *LRUQueue) TrimExcess() {
   211  	threshold := float64(len(lru.array)) * 0.9
   212  	if lru.size < int(threshold) {
   213  		lru.setCapacity(lru.size)
   214  	}
   215  }
   216  
   217  //
   218  // internal helpers
   219  //
   220  
   221  func (lru *LRUQueue) growCapacity() int {
   222  	size := len(lru.array)
   223  	newCapacity := size << 1
   224  	minimumGrow := size + ringBufferMinimumGrow
   225  
   226  	if newCapacity < minimumGrow {
   227  		newCapacity = minimumGrow
   228  	}
   229  
   230  	return newCapacity
   231  }
   232  
   233  func (lru *LRUQueue) setCapacity(capacity int) {
   234  	newArray := make([]*Value, capacity)
   235  	if lru.size > 0 {
   236  		if lru.head < lru.tail {
   237  			arrayCopy(lru.array, lru.head, newArray, 0, lru.size)
   238  		} else {
   239  			arrayCopy(lru.array, lru.head, newArray, 0, len(lru.array)-lru.head)
   240  			arrayCopy(lru.array, 0, newArray, len(lru.array)-lru.head, lru.tail)
   241  		}
   242  	}
   243  	lru.array = newArray
   244  	lru.head = 0
   245  	if lru.size == capacity {
   246  		lru.tail = 0
   247  	} else {
   248  		lru.tail = lru.size
   249  	}
   250  }
   251  
   252  //
   253  // array helpers
   254  //
   255  
   256  func arrayClear(source []*Value, index, length int) {
   257  	for x := 0; x < length; x++ {
   258  		absoluteIndex := x + index
   259  		source[absoluteIndex] = nil
   260  	}
   261  }
   262  
   263  func arrayCopy(source []*Value, sourceIndex int, destination []*Value, destinationIndex, length int) {
   264  	for x := 0; x < length; x++ {
   265  		from := sourceIndex + x
   266  		to := destinationIndex + x
   267  
   268  		destination[to] = source[from]
   269  	}
   270  }
   271  
   272  const (
   273  	ringBufferMinimumGrow     = 4
   274  	ringBufferDefaultCapacity = 4
   275  )