mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 05:32:26 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			706 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			706 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package structr
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"reflect"
 | 
						|
	"sync"
 | 
						|
	"unsafe"
 | 
						|
)
 | 
						|
 | 
						|
// DefaultIgnoreErr is the default function used to
 | 
						|
// ignore (i.e. not cache) incoming error results during
 | 
						|
// Load() calls. By default ignores context pkg errors.
 | 
						|
func DefaultIgnoreErr(err error) bool {
 | 
						|
	return errors.Is(err, context.Canceled) ||
 | 
						|
		errors.Is(err, context.DeadlineExceeded)
 | 
						|
}
 | 
						|
 | 
						|
// CacheConfig defines config vars
 | 
						|
// for initializing a Cache{} type.
 | 
						|
type CacheConfig[StructType any] struct {
 | 
						|
 | 
						|
	// IgnoreErr defines which errors to
 | 
						|
	// ignore (i.e. not cache) returned
 | 
						|
	// from load function callback calls.
 | 
						|
	// This may be left as nil, on which
 | 
						|
	// DefaultIgnoreErr will be used.
 | 
						|
	IgnoreErr func(error) bool
 | 
						|
 | 
						|
	// Copy provides a means of copying
 | 
						|
	// cached values, to ensure returned values
 | 
						|
	// do not share memory with those in cache.
 | 
						|
	Copy func(StructType) StructType
 | 
						|
 | 
						|
	// Invalidate is called when cache values
 | 
						|
	// (NOT errors) are invalidated, either
 | 
						|
	// as the values passed to Put() / Store(),
 | 
						|
	// or by the keys by calls to Invalidate().
 | 
						|
	Invalidate func(StructType)
 | 
						|
 | 
						|
	// Indices defines indices to create
 | 
						|
	// in the Cache for the receiving
 | 
						|
	// generic struct type parameter.
 | 
						|
	Indices []IndexConfig
 | 
						|
 | 
						|
	// MaxSize defines the maximum number
 | 
						|
	// of items allowed in the Cache at
 | 
						|
	// one time, before old items start
 | 
						|
	// getting evicted.
 | 
						|
	MaxSize int
 | 
						|
}
 | 
						|
 | 
						|
// Cache provides a structure cache with automated
 | 
						|
// indexing and lookups by any initialization-defined
 | 
						|
// combination of fields. This also supports caching
 | 
						|
// of negative results (errors!) returned by LoadOne().
 | 
						|
type Cache[StructType any] struct {
 | 
						|
 | 
						|
	// hook functions.
 | 
						|
	ignore  func(error) bool
 | 
						|
	copy    func(StructType) StructType
 | 
						|
	invalid func(StructType)
 | 
						|
 | 
						|
	// keeps track of all indexed items,
 | 
						|
	// in order of last recently used (LRU).
 | 
						|
	lru list
 | 
						|
 | 
						|
	// indices used in storing passed struct
 | 
						|
	// types by user defined sets of fields.
 | 
						|
	indices []Index
 | 
						|
 | 
						|
	// max cache size, imposes size
 | 
						|
	// limit on the lru list in order
 | 
						|
	// to evict old entries.
 | 
						|
	maxSize int
 | 
						|
 | 
						|
	// protective mutex, guards:
 | 
						|
	// - Cache{}.*
 | 
						|
	// - Index{}.data
 | 
						|
	mutex sync.Mutex
 | 
						|
}
 | 
						|
 | 
						|
// Init initializes the cache with given configuration
 | 
						|
// including struct fields to index, and necessary fns.
 | 
						|
func (c *Cache[T]) Init(config CacheConfig[T]) {
 | 
						|
	t := reflect.TypeOf((*T)(nil)).Elem()
 | 
						|
 | 
						|
	if len(config.Indices) == 0 {
 | 
						|
		panic("no indices provided")
 | 
						|
	}
 | 
						|
 | 
						|
	if config.IgnoreErr == nil {
 | 
						|
		config.IgnoreErr = DefaultIgnoreErr
 | 
						|
	}
 | 
						|
 | 
						|
	if config.Copy == nil {
 | 
						|
		panic("copy function must be provided")
 | 
						|
	}
 | 
						|
 | 
						|
	if config.MaxSize < 2 {
 | 
						|
		panic("minimum cache size is 2 for LRU to work")
 | 
						|
	}
 | 
						|
 | 
						|
	// Safely copy over
 | 
						|
	// provided config.
 | 
						|
	c.mutex.Lock()
 | 
						|
	defer c.mutex.Unlock()
 | 
						|
	c.indices = make([]Index, len(config.Indices))
 | 
						|
	for i, cfg := range config.Indices {
 | 
						|
		c.indices[i].ptr = unsafe.Pointer(c)
 | 
						|
		c.indices[i].init(t, cfg, config.MaxSize)
 | 
						|
	}
 | 
						|
	c.ignore = config.IgnoreErr
 | 
						|
	c.copy = config.Copy
 | 
						|
	c.invalid = config.Invalidate
 | 
						|
	c.maxSize = config.MaxSize
 | 
						|
}
 | 
						|
 | 
						|
// Index selects index with given name from cache, else panics.
 | 
						|
func (c *Cache[T]) Index(name string) *Index {
 | 
						|
	for i, idx := range c.indices {
 | 
						|
		if idx.name == name {
 | 
						|
			return &(c.indices[i])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	panic("unknown index: " + name)
 | 
						|
}
 | 
						|
 | 
						|
// GetOne fetches value from cache stored under index, using precalculated index key.
 | 
						|
func (c *Cache[T]) GetOne(index *Index, key Key) (T, bool) {
 | 
						|
	values := c.Get(index, key)
 | 
						|
	if len(values) == 0 {
 | 
						|
		var zero T
 | 
						|
		return zero, false
 | 
						|
	}
 | 
						|
	return values[0], true
 | 
						|
}
 | 
						|
 | 
						|
// Get fetches values from the cache stored under index, using precalculated index keys.
 | 
						|
func (c *Cache[T]) Get(index *Index, keys ...Key) []T {
 | 
						|
	if index == nil {
 | 
						|
		panic("no index given")
 | 
						|
	} else if index.ptr != unsafe.Pointer(c) {
 | 
						|
		panic("invalid index for cache")
 | 
						|
	}
 | 
						|
 | 
						|
	// Preallocate expected ret slice.
 | 
						|
	values := make([]T, 0, len(keys))
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
	defer c.mutex.Unlock()
 | 
						|
 | 
						|
	// Check cache init.
 | 
						|
	if c.copy == nil {
 | 
						|
		panic("not initialized")
 | 
						|
	}
 | 
						|
 | 
						|
	for i := range keys {
 | 
						|
		// Concatenate all *values* from cached items.
 | 
						|
		index.get(keys[i].key, func(item *indexed_item) {
 | 
						|
			if value, ok := item.data.(T); ok {
 | 
						|
 | 
						|
				// Append value COPY.
 | 
						|
				value = c.copy(value)
 | 
						|
				values = append(values, value)
 | 
						|
 | 
						|
				// Push to front of LRU list, USING
 | 
						|
				// THE ITEM'S LRU ENTRY, NOT THE
 | 
						|
				// INDEX KEY ENTRY. VERY IMPORTANT!!
 | 
						|
				c.lru.move_front(&item.elem)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	return values
 | 
						|
}
 | 
						|
 | 
						|
// Put will insert the given values into cache,
 | 
						|
// calling any invalidate hook on each value.
 | 
						|
func (c *Cache[T]) Put(values ...T) {
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Wrap unlock to only do once.
 | 
						|
	unlock := once(c.mutex.Unlock)
 | 
						|
	defer unlock()
 | 
						|
 | 
						|
	// Check cache init.
 | 
						|
	if c.copy == nil {
 | 
						|
		panic("not initialized")
 | 
						|
	}
 | 
						|
 | 
						|
	// Store all passed values.
 | 
						|
	for i := range values {
 | 
						|
		c.store_value(
 | 
						|
			nil, "",
 | 
						|
			values[i],
 | 
						|
		)
 | 
						|
	}
 | 
						|
 | 
						|
	// Get func ptrs.
 | 
						|
	invalid := c.invalid
 | 
						|
 | 
						|
	// Done with
 | 
						|
	// the lock.
 | 
						|
	unlock()
 | 
						|
 | 
						|
	if invalid != nil {
 | 
						|
		// Pass all invalidated values
 | 
						|
		// to given user hook (if set).
 | 
						|
		for _, value := range values {
 | 
						|
			invalid(value)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// LoadOneBy fetches one result from the cache stored under index, using precalculated index key.
 | 
						|
// In the case that no result is found, provided load callback will be used to hydrate the cache.
 | 
						|
func (c *Cache[T]) LoadOne(index *Index, key Key, load func() (T, error)) (T, error) {
 | 
						|
	if index == nil {
 | 
						|
		panic("no index given")
 | 
						|
	} else if index.ptr != unsafe.Pointer(c) {
 | 
						|
		panic("invalid index for cache")
 | 
						|
	} else if !is_unique(index.flags) {
 | 
						|
		panic("cannot get one by non-unique index")
 | 
						|
	}
 | 
						|
 | 
						|
	var (
 | 
						|
		// whether an item was found
 | 
						|
		// (and so val / err are set).
 | 
						|
		ok bool
 | 
						|
 | 
						|
		// separate value / error ptrs
 | 
						|
		// as the item is liable to
 | 
						|
		// change outside of lock.
 | 
						|
		val T
 | 
						|
		err error
 | 
						|
	)
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Wrap unlock to only do once.
 | 
						|
	unlock := once(c.mutex.Unlock)
 | 
						|
	defer unlock()
 | 
						|
 | 
						|
	// Check init'd.
 | 
						|
	if c.copy == nil ||
 | 
						|
		c.ignore == nil {
 | 
						|
		panic("not initialized")
 | 
						|
	}
 | 
						|
 | 
						|
	// Get item indexed at key.
 | 
						|
	item := index.get_one(key)
 | 
						|
 | 
						|
	if ok = (item != nil); ok {
 | 
						|
		var is bool
 | 
						|
 | 
						|
		if val, is = item.data.(T); is {
 | 
						|
			// Set value COPY.
 | 
						|
			val = c.copy(val)
 | 
						|
 | 
						|
			// Push to front of LRU list, USING
 | 
						|
			// THE ITEM'S LRU ENTRY, NOT THE
 | 
						|
			// INDEX KEY ENTRY. VERY IMPORTANT!!
 | 
						|
			c.lru.move_front(&item.elem)
 | 
						|
 | 
						|
		} else {
 | 
						|
 | 
						|
			// Attempt to return error.
 | 
						|
			err, _ = item.data.(error)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Get func ptrs.
 | 
						|
	ignore := c.ignore
 | 
						|
 | 
						|
	// Done with
 | 
						|
	// the lock.
 | 
						|
	unlock()
 | 
						|
 | 
						|
	if ok {
 | 
						|
		// item found!
 | 
						|
		return val, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Load new result.
 | 
						|
	val, err = load()
 | 
						|
 | 
						|
	// Check for ignored error types.
 | 
						|
	if err != nil && ignore(err) {
 | 
						|
		return val, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Index this new loaded item.
 | 
						|
	// Note this handles copying of
 | 
						|
	// the provided value, so it is
 | 
						|
	// safe for us to return as-is.
 | 
						|
	if err != nil {
 | 
						|
		c.store_error(index, key.key, err)
 | 
						|
	} else {
 | 
						|
		c.store_value(index, key.key, val)
 | 
						|
	}
 | 
						|
 | 
						|
	// Done with lock.
 | 
						|
	c.mutex.Unlock()
 | 
						|
 | 
						|
	return val, err
 | 
						|
}
 | 
						|
 | 
						|
// Load fetches values from the cache stored under index, using precalculated index keys. The cache will attempt to
 | 
						|
// results with values stored under keys, passing keys with uncached results to the provider load callback to further
 | 
						|
// hydrate the cache with missing results. Cached error results not included or returned by this function.
 | 
						|
func (c *Cache[T]) Load(index *Index, keys []Key, load func([]Key) ([]T, error)) ([]T, error) {
 | 
						|
	if index == nil {
 | 
						|
		panic("no index given")
 | 
						|
	} else if index.ptr != unsafe.Pointer(c) {
 | 
						|
		panic("invalid index for cache")
 | 
						|
	}
 | 
						|
 | 
						|
	// Preallocate expected ret slice.
 | 
						|
	values := make([]T, 0, len(keys))
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Wrap unlock to only do once.
 | 
						|
	unlock := once(c.mutex.Unlock)
 | 
						|
	defer unlock()
 | 
						|
 | 
						|
	// Check init'd.
 | 
						|
	if c.copy == nil {
 | 
						|
		panic("not initialized")
 | 
						|
	}
 | 
						|
 | 
						|
	// Iterate keys and catch uncached.
 | 
						|
	toLoad := make([]Key, 0, len(keys))
 | 
						|
	for _, key := range keys {
 | 
						|
 | 
						|
		// Value length before
 | 
						|
		// any below appends.
 | 
						|
		before := len(values)
 | 
						|
 | 
						|
		// Concatenate all *values* from cached items.
 | 
						|
		index.get(key.key, func(item *indexed_item) {
 | 
						|
			if value, ok := item.data.(T); ok {
 | 
						|
				// Append value COPY.
 | 
						|
				value = c.copy(value)
 | 
						|
				values = append(values, value)
 | 
						|
 | 
						|
				// Push to front of LRU list, USING
 | 
						|
				// THE ITEM'S LRU ENTRY, NOT THE
 | 
						|
				// INDEX KEY ENTRY. VERY IMPORTANT!!
 | 
						|
				c.lru.move_front(&item.elem)
 | 
						|
			}
 | 
						|
		})
 | 
						|
 | 
						|
		// Only if values changed did
 | 
						|
		// we actually find anything.
 | 
						|
		if len(values) == before {
 | 
						|
			toLoad = append(toLoad, key)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Done with
 | 
						|
	// the lock.
 | 
						|
	unlock()
 | 
						|
 | 
						|
	if len(toLoad) == 0 {
 | 
						|
		// We loaded everything!
 | 
						|
		return values, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Load uncached key values.
 | 
						|
	uncached, err := load(toLoad)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Store all uncached values.
 | 
						|
	for i := range uncached {
 | 
						|
		c.store_value(
 | 
						|
			nil, "",
 | 
						|
			uncached[i],
 | 
						|
		)
 | 
						|
	}
 | 
						|
 | 
						|
	// Done with lock.
 | 
						|
	c.mutex.Unlock()
 | 
						|
 | 
						|
	// Append uncached to return values.
 | 
						|
	values = append(values, uncached...)
 | 
						|
 | 
						|
	return values, nil
 | 
						|
}
 | 
						|
 | 
						|
// Store will call the given store callback, on non-error then
 | 
						|
// passing the provided value to the Put() function. On error
 | 
						|
// return the value is still passed to stored invalidate hook.
 | 
						|
func (c *Cache[T]) Store(value T, store func() error) error {
 | 
						|
	// Store value.
 | 
						|
	err := store()
 | 
						|
	if err != nil {
 | 
						|
 | 
						|
		// Get func ptrs.
 | 
						|
		c.mutex.Lock()
 | 
						|
		invalid := c.invalid
 | 
						|
		c.mutex.Unlock()
 | 
						|
 | 
						|
		// On error don't store
 | 
						|
		// value, but still pass
 | 
						|
		// to invalidate hook.
 | 
						|
		if invalid != nil {
 | 
						|
			invalid(value)
 | 
						|
		}
 | 
						|
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Store value.
 | 
						|
	c.Put(value)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Invalidate invalidates all results stored under index keys.
 | 
						|
// Note that if set, this will call the invalidate hook on each.
 | 
						|
func (c *Cache[T]) Invalidate(index *Index, keys ...Key) {
 | 
						|
	if index == nil {
 | 
						|
		panic("no index given")
 | 
						|
	} else if index.ptr != unsafe.Pointer(c) {
 | 
						|
		panic("invalid index for cache")
 | 
						|
	}
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Preallocate expected ret slice.
 | 
						|
	values := make([]T, 0, len(keys))
 | 
						|
 | 
						|
	for i := range keys {
 | 
						|
		// Delete all items under key from index, collecting
 | 
						|
		// value items and dropping them from all their indices.
 | 
						|
		index.delete(keys[i].key, func(item *indexed_item) {
 | 
						|
 | 
						|
			if value, ok := item.data.(T); ok {
 | 
						|
				// No need to copy, as item
 | 
						|
				// being deleted from cache.
 | 
						|
				values = append(values, value)
 | 
						|
			}
 | 
						|
 | 
						|
			// Delete item.
 | 
						|
			c.delete(item)
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	// Get func ptrs.
 | 
						|
	invalid := c.invalid
 | 
						|
 | 
						|
	// Done with lock.
 | 
						|
	c.mutex.Unlock()
 | 
						|
 | 
						|
	if invalid != nil {
 | 
						|
		// Pass all invalidated values
 | 
						|
		// to given user hook (if set).
 | 
						|
		for _, value := range values {
 | 
						|
			invalid(value)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Trim will truncate the cache to ensure it
 | 
						|
// stays within given percentage of MaxSize.
 | 
						|
func (c *Cache[T]) Trim(perc float64) {
 | 
						|
 | 
						|
	// Acquire lock.
 | 
						|
	c.mutex.Lock()
 | 
						|
 | 
						|
	// Calculate number of cache items to drop.
 | 
						|
	max := (perc / 100) * float64(c.maxSize)
 | 
						|
	diff := c.lru.len - int(max)
 | 
						|
	if diff <= 0 {
 | 
						|
 | 
						|
		// Trim not needed.
 | 
						|
		c.mutex.Unlock()
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Iterate over 'diff' items
 | 
						|
	// from back (oldest) of cache.
 | 
						|
	for i := 0; i < diff; i++ {
 | 
						|
 | 
						|
		// Get oldest LRU elem.
 | 
						|
		oldest := c.lru.tail
 | 
						|
		if oldest == nil {
 | 
						|
 | 
						|
			// reached
 | 
						|
			// end.
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Drop oldest item from cache.
 | 
						|
		item := (*indexed_item)(oldest.data)
 | 
						|
		c.delete(item)
 | 
						|
	}
 | 
						|
 | 
						|
	// Compact index data stores.
 | 
						|
	for _, idx := range c.indices {
 | 
						|
		(&idx).data.Compact()
 | 
						|
	}
 | 
						|
 | 
						|
	// Done with lock.
 | 
						|
	c.mutex.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// Clear empties the cache by calling .Trim(0).
 | 
						|
func (c *Cache[T]) Clear() { c.Trim(0) }
 | 
						|
 | 
						|
// Len returns the current length of cache.
 | 
						|
func (c *Cache[T]) Len() int {
 | 
						|
	c.mutex.Lock()
 | 
						|
	l := c.lru.len
 | 
						|
	c.mutex.Unlock()
 | 
						|
	return l
 | 
						|
}
 | 
						|
 | 
						|
// Debug returns debug stats about cache.
 | 
						|
func (c *Cache[T]) Debug() map[string]any {
 | 
						|
	m := make(map[string]any, 2)
 | 
						|
	c.mutex.Lock()
 | 
						|
	m["lru"] = c.lru.len
 | 
						|
	indices := make(map[string]any, len(c.indices))
 | 
						|
	m["indices"] = indices
 | 
						|
	for _, idx := range c.indices {
 | 
						|
		var n uint64
 | 
						|
		for _, l := range idx.data.m {
 | 
						|
			n += uint64(l.len)
 | 
						|
		}
 | 
						|
		indices[idx.name] = n
 | 
						|
	}
 | 
						|
	c.mutex.Unlock()
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
// Cap returns the maximum capacity (size) of cache.
 | 
						|
func (c *Cache[T]) Cap() int {
 | 
						|
	c.mutex.Lock()
 | 
						|
	m := c.maxSize
 | 
						|
	c.mutex.Unlock()
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cache[T]) store_value(index *Index, key string, value T) {
 | 
						|
	// Alloc new index item.
 | 
						|
	item := new_indexed_item()
 | 
						|
	if cap(item.indexed) < len(c.indices) {
 | 
						|
 | 
						|
		// Preallocate item indices slice to prevent Go auto
 | 
						|
		// allocating overlying large slices we don't need.
 | 
						|
		item.indexed = make([]*index_entry, 0, len(c.indices))
 | 
						|
	}
 | 
						|
 | 
						|
	// Create COPY of value.
 | 
						|
	value = c.copy(value)
 | 
						|
	item.data = value
 | 
						|
 | 
						|
	if index != nil {
 | 
						|
		// Append item to index a key
 | 
						|
		// was already generated for.
 | 
						|
		evicted := index.append(key, item)
 | 
						|
		if evicted != nil {
 | 
						|
 | 
						|
			// This item is no longer
 | 
						|
			// indexed, remove from list.
 | 
						|
			c.lru.remove(&evicted.elem)
 | 
						|
			free_indexed_item(evicted)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Get ptr to value data.
 | 
						|
	ptr := unsafe.Pointer(&value)
 | 
						|
 | 
						|
	// Acquire key buf.
 | 
						|
	buf := new_buffer()
 | 
						|
 | 
						|
	for i := range c.indices {
 | 
						|
		// Get current index ptr.
 | 
						|
		idx := (&c.indices[i])
 | 
						|
		if idx == index {
 | 
						|
 | 
						|
			// Already stored under
 | 
						|
			// this index, ignore.
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Extract fields comprising index key.
 | 
						|
		parts := extract_fields(ptr, idx.fields)
 | 
						|
 | 
						|
		// Calculate index key.
 | 
						|
		key := idx.key(buf, parts)
 | 
						|
		if key == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Append item to this index.
 | 
						|
		evicted := idx.append(key, item)
 | 
						|
		if evicted != nil {
 | 
						|
 | 
						|
			// This item is no longer
 | 
						|
			// indexed, remove from list.
 | 
						|
			c.lru.remove(&evicted.elem)
 | 
						|
			free_indexed_item(evicted)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Done with buf.
 | 
						|
	free_buffer(buf)
 | 
						|
 | 
						|
	if len(item.indexed) == 0 {
 | 
						|
		// Item was not stored under
 | 
						|
		// any index. Drop this item.
 | 
						|
		free_indexed_item(item)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Add item to main lru list.
 | 
						|
	c.lru.push_front(&item.elem)
 | 
						|
 | 
						|
	if c.lru.len > c.maxSize {
 | 
						|
		// Cache has hit max size!
 | 
						|
		// Drop the oldest element.
 | 
						|
		ptr := c.lru.tail.data
 | 
						|
		item := (*indexed_item)(ptr)
 | 
						|
		c.delete(item)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cache[T]) store_error(index *Index, key string, err error) {
 | 
						|
	if index == nil {
 | 
						|
		// nothing we
 | 
						|
		// can do here.
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Alloc new index item.
 | 
						|
	item := new_indexed_item()
 | 
						|
	if cap(item.indexed) < len(c.indices) {
 | 
						|
 | 
						|
		// Preallocate item indices slice to prevent Go auto
 | 
						|
		// allocating overlying large slices we don't need.
 | 
						|
		item.indexed = make([]*index_entry, 0, len(c.indices))
 | 
						|
	}
 | 
						|
 | 
						|
	// Set error val.
 | 
						|
	item.data = err
 | 
						|
 | 
						|
	// Append item to index a key
 | 
						|
	// was already generated for.
 | 
						|
	evicted := index.append(key, item)
 | 
						|
	if evicted != nil {
 | 
						|
 | 
						|
		// This item is no longer
 | 
						|
		// indexed, remove from list.
 | 
						|
		c.lru.remove(&evicted.elem)
 | 
						|
		free_indexed_item(evicted)
 | 
						|
	}
 | 
						|
 | 
						|
	// Add item to main lru list.
 | 
						|
	c.lru.push_front(&item.elem)
 | 
						|
 | 
						|
	if c.lru.len > c.maxSize {
 | 
						|
		// Cache has hit max size!
 | 
						|
		// Drop the oldest element.
 | 
						|
		ptr := c.lru.tail.data
 | 
						|
		item := (*indexed_item)(ptr)
 | 
						|
		c.delete(item)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *Cache[T]) delete(i *indexed_item) {
 | 
						|
	for len(i.indexed) != 0 {
 | 
						|
		// Pop last indexed entry from list.
 | 
						|
		entry := i.indexed[len(i.indexed)-1]
 | 
						|
		i.indexed[len(i.indexed)-1] = nil
 | 
						|
		i.indexed = i.indexed[:len(i.indexed)-1]
 | 
						|
 | 
						|
		// Get entry's index.
 | 
						|
		index := entry.index
 | 
						|
 | 
						|
		// Drop this index_entry.
 | 
						|
		index.delete_entry(entry)
 | 
						|
	}
 | 
						|
 | 
						|
	// Drop from lru list.
 | 
						|
	c.lru.remove(&i.elem)
 | 
						|
 | 
						|
	// Free unused item.
 | 
						|
	free_indexed_item(i)
 | 
						|
}
 |