diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-structr/cache.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-structr/cache.go | 675 |
1 files changed, 0 insertions, 675 deletions
diff --git a/vendor/codeberg.org/gruf/go-structr/cache.go b/vendor/codeberg.org/gruf/go-structr/cache.go deleted file mode 100644 index 3705d7c7c..000000000 --- a/vendor/codeberg.org/gruf/go-structr/cache.go +++ /dev/null @@ -1,675 +0,0 @@ -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 struct cache. -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 lruList in order - // to evict old entries. - maxSize int - - // protective mutex, guards: - // - Cache{}.lruList - // - Index{}.data - // - Cache{} hook fns - 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() - 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 - c.mutex.Unlock() -} - -// 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. -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 cached. - 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. - index.append(&c.lru, key, item) - } - - // 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) - if parts == nil { - continue - } - - // Calculate index key. - key := idx.key(buf, parts) - if key == "" { - continue - } - - // Append item to this index. - idx.append(&c.lru, key, item) - } - - // Add item to main lru list. - c.lru.push_front(&item.elem) - - // Done with buf. - free_buffer(buf) - - 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. - index.append(&c.lru, key, item) - - // 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(item *indexed_item) { - for len(item.indexed) != 0 { - // Pop last indexed entry from list. - entry := item.indexed[len(item.indexed)-1] - item.indexed = item.indexed[:len(item.indexed)-1] - - // Drop index_entry from index. - entry.index.delete_entry(entry) - } - - // Drop entry from lru list. - c.lru.remove(&item.elem) - - // Free now-unused item. - free_indexed_item(item) -} |