mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
9d52f1b018
- [x] Reorganize golangci-lint configuration with documented disable reasons - [x] Simplify errcheck and revive linter rules with targeted exclusions - [x] Pre-compile regex patterns in input_validation.go for performance - [x] Fix type assertions in memory_shard.go and resp.go with safety checks - [x] Replace string comparison with EqualFold for case-insensitive matching - [x] Fix loop variable captures in jwk.go and logout.go - [x] Change high goroutine log level from Info to Debug in autocleanup.go - [x] Replace deprecated "cancelled" spelling with "canceled" throughout - [x] Add nolint annotations for intentional unused parameters - [x] Improve comment formatting for deprecated functions - [x] Fix comment spelling: "marshalling" → "marshaling" - [x] Refactor provider warnings formatting in internal/providers/warnings.go - [x] Simplify metrics summary building in internal/recovery/metrics.go - [x] Pre-allocate slice in error_recovery.go GetDegradedServices - [x] Refactor context cancellation checks in redis.go
295 lines
6.0 KiB
Go
295 lines
6.0 KiB
Go
package backends
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// cacheShard represents a single shard of the sharded cache
|
|
// Each shard has its own lock for reduced contention
|
|
type cacheShard struct {
|
|
items map[string]*memoryCacheItem
|
|
lruList *list.List
|
|
mu sync.RWMutex
|
|
maxSize int64
|
|
maxMemory int64
|
|
size int64
|
|
memoryUsed int64
|
|
}
|
|
|
|
// newCacheShard creates a new cache shard
|
|
func newCacheShard(maxSize, maxMemory int64) *cacheShard {
|
|
return &cacheShard{
|
|
items: make(map[string]*memoryCacheItem),
|
|
lruList: list.New(),
|
|
maxSize: maxSize,
|
|
maxMemory: maxMemory,
|
|
}
|
|
}
|
|
|
|
// get retrieves a value from this shard
|
|
// Returns: value, exists, expired
|
|
func (s *cacheShard) get(key string) (interface{}, bool, bool) {
|
|
s.mu.RLock()
|
|
item, exists := s.items[key]
|
|
s.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return nil, false, false
|
|
}
|
|
|
|
if item.isExpired() {
|
|
return nil, true, true // exists but expired
|
|
}
|
|
|
|
// Update access time and LRU position under write lock
|
|
s.mu.Lock()
|
|
// Re-check item exists (could have been deleted)
|
|
item, exists = s.items[key]
|
|
if exists && !item.isExpired() {
|
|
item.accessedAt = time.Now()
|
|
item.accessCount++
|
|
if elem, ok := item.element.(*list.Element); ok && elem != nil {
|
|
s.lruList.MoveToFront(elem)
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
if !exists || item.isExpired() {
|
|
return nil, false, false
|
|
}
|
|
|
|
return item.value, true, false
|
|
}
|
|
|
|
// set stores a value in this shard
|
|
func (s *cacheShard) set(key string, value interface{}, expiresAt time.Time, size int64) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Check if we need to evict items
|
|
if s.maxSize > 0 && s.size >= s.maxSize {
|
|
s.evictLRULocked()
|
|
}
|
|
if s.maxMemory > 0 && s.memoryUsed+size > s.maxMemory {
|
|
s.evictLRULocked()
|
|
}
|
|
|
|
// Remove old item if exists
|
|
if oldItem, exists := s.items[key]; exists {
|
|
s.memoryUsed -= oldItem.size
|
|
if elem, ok := oldItem.element.(*list.Element); ok && elem != nil {
|
|
s.lruList.Remove(elem)
|
|
}
|
|
s.size--
|
|
}
|
|
|
|
now := time.Now()
|
|
item := &memoryCacheItem{
|
|
key: key,
|
|
value: value,
|
|
expiresAt: expiresAt,
|
|
createdAt: now,
|
|
accessedAt: now,
|
|
accessCount: 0,
|
|
size: size,
|
|
}
|
|
|
|
item.element = s.lruList.PushFront(item)
|
|
s.items[key] = item
|
|
s.size++
|
|
s.memoryUsed += size
|
|
}
|
|
|
|
// delete removes a key from this shard
|
|
// Returns true if the key was deleted
|
|
func (s *cacheShard) delete(key string) bool {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
item, exists := s.items[key]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
s.deleteItemLocked(item)
|
|
return true
|
|
}
|
|
|
|
// exists checks if a key exists (and is not expired)
|
|
func (s *cacheShard) exists(key string) bool {
|
|
s.mu.RLock()
|
|
item, exists := s.items[key]
|
|
s.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
return !item.isExpired()
|
|
}
|
|
|
|
// ttl returns the remaining TTL for a key
|
|
func (s *cacheShard) ttl(key string) (time.Duration, bool) {
|
|
s.mu.RLock()
|
|
item, exists := s.items[key]
|
|
s.mu.RUnlock()
|
|
|
|
if !exists || item.isExpired() {
|
|
return 0, false
|
|
}
|
|
|
|
if item.expiresAt.IsZero() {
|
|
return 0, true // No expiration
|
|
}
|
|
|
|
remaining := time.Until(item.expiresAt)
|
|
if remaining < 0 {
|
|
return 0, false
|
|
}
|
|
|
|
return remaining, true
|
|
}
|
|
|
|
// expire updates the TTL for an existing key
|
|
func (s *cacheShard) expire(key string, ttl time.Duration) bool {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
item, exists := s.items[key]
|
|
if !exists || item.isExpired() {
|
|
return false
|
|
}
|
|
|
|
if ttl > 0 {
|
|
item.expiresAt = time.Now().Add(ttl)
|
|
} else {
|
|
item.expiresAt = time.Time{} // Remove expiration
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// keys returns all non-expired keys matching the pattern
|
|
func (s *cacheShard) keys(pattern string) []string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var keys []string
|
|
for key, item := range s.items {
|
|
if !item.isExpired() && matchPattern(pattern, key) {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// clear removes all items from this shard
|
|
func (s *cacheShard) clear() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.items = make(map[string]*memoryCacheItem)
|
|
s.lruList.Init()
|
|
s.size = 0
|
|
s.memoryUsed = 0
|
|
}
|
|
|
|
// cleanup removes expired items
|
|
// Returns the number of items removed
|
|
func (s *cacheShard) cleanup() int {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
var toRemove []*memoryCacheItem
|
|
for _, item := range s.items {
|
|
if item.isExpired() {
|
|
toRemove = append(toRemove, item)
|
|
}
|
|
}
|
|
|
|
for _, item := range toRemove {
|
|
s.deleteItemLocked(item)
|
|
}
|
|
|
|
return len(toRemove)
|
|
}
|
|
|
|
// stats returns statistics for this shard
|
|
func (s *cacheShard) stats() (size, memory int64) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.size, s.memoryUsed
|
|
}
|
|
|
|
// deleteItemLocked removes an item (must be called with lock held)
|
|
func (s *cacheShard) deleteItemLocked(item *memoryCacheItem) {
|
|
if elem, ok := item.element.(*list.Element); ok && elem != nil {
|
|
s.lruList.Remove(elem)
|
|
}
|
|
delete(s.items, item.key)
|
|
s.size--
|
|
s.memoryUsed -= item.size
|
|
}
|
|
|
|
// evictLRULocked evicts the least recently used item (must be called with lock held)
|
|
func (s *cacheShard) evictLRULocked() bool {
|
|
if s.lruList.Len() == 0 {
|
|
return false
|
|
}
|
|
|
|
element := s.lruList.Back()
|
|
if element != nil {
|
|
item, ok := element.Value.(*memoryCacheItem)
|
|
if ok {
|
|
s.deleteItemLocked(item)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// evictOne evicts one item from this shard (for global limit enforcement)
|
|
func (s *cacheShard) evictOne() bool {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.evictLRULocked()
|
|
}
|
|
|
|
// getOldestAccessTime returns the access time of the LRU item (oldest) in this shard
|
|
// Returns zero time if shard is empty
|
|
func (s *cacheShard) getOldestAccessTime() time.Time {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
if s.lruList.Len() == 0 {
|
|
return time.Time{}
|
|
}
|
|
|
|
element := s.lruList.Back()
|
|
if element != nil {
|
|
item, ok := element.Value.(*memoryCacheItem)
|
|
if ok {
|
|
return item.accessedAt
|
|
}
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// fnv32 computes FNV-1a hash of a string
|
|
// This is a fast, well-distributed hash function
|
|
func fnv32(key string) uint32 {
|
|
const (
|
|
offset32 = uint32(2166136261)
|
|
prime32 = uint32(16777619)
|
|
)
|
|
|
|
hash := offset32
|
|
for i := 0; i < len(key); i++ {
|
|
hash ^= uint32(key[i])
|
|
hash *= prime32
|
|
}
|
|
return hash
|
|
}
|