mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
418 lines
9.2 KiB
Go
418 lines
9.2 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// CacheStrategy defines the caching strategy interface
|
|
type CacheStrategy interface {
|
|
// Name returns the strategy name for debugging
|
|
Name() string
|
|
// ShouldEvict determines if an item should be evicted
|
|
ShouldEvict(item interface{}, now time.Time) bool
|
|
// OnAccess is called when an item is accessed
|
|
OnAccess(key string, item interface{})
|
|
// EstimateSize estimates the memory size of an item
|
|
EstimateSize(item interface{}) int64
|
|
}
|
|
|
|
// UnifiedCacheConfig provides configuration for the unified cache
|
|
type UnifiedCacheConfig struct {
|
|
MaxSize int
|
|
MaxMemoryBytes int64
|
|
CleanupInterval time.Duration
|
|
Strategy CacheStrategy
|
|
EnableMemoryLimit bool
|
|
EnableAutoCleanup bool
|
|
Logger *Logger
|
|
}
|
|
|
|
// DefaultUnifiedCacheConfig returns default cache configuration
|
|
func DefaultUnifiedCacheConfig() UnifiedCacheConfig {
|
|
return UnifiedCacheConfig{
|
|
MaxSize: DefaultMaxSize,
|
|
MaxMemoryBytes: 64 * 1024 * 1024, // 64MB default
|
|
CleanupInterval: 2 * time.Minute,
|
|
EnableMemoryLimit: false,
|
|
EnableAutoCleanup: true,
|
|
Logger: nil,
|
|
}
|
|
}
|
|
|
|
// UnifiedCache provides a single, flexible cache implementation
|
|
// that can be configured with different strategies and features
|
|
type UnifiedCache struct {
|
|
items map[string]interface{}
|
|
strategy CacheStrategy
|
|
cleanupTask *BackgroundTask
|
|
logger *Logger
|
|
config UnifiedCacheConfig
|
|
currentMemoryBytes int64
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewUnifiedCache creates a new unified cache with the given configuration
|
|
func NewUnifiedCache(config UnifiedCacheConfig) *UnifiedCache {
|
|
if config.Logger == nil {
|
|
config.Logger = GetSingletonNoOpLogger()
|
|
}
|
|
|
|
if config.Strategy == nil {
|
|
config.Strategy = NewLRUStrategy(config.MaxSize)
|
|
}
|
|
|
|
c := &UnifiedCache{
|
|
items: make(map[string]interface{}, config.MaxSize),
|
|
strategy: config.Strategy,
|
|
config: config,
|
|
logger: config.Logger,
|
|
}
|
|
|
|
if config.EnableAutoCleanup {
|
|
c.startAutoCleanup()
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
// Set stores a value in the cache
|
|
func (c *UnifiedCache) Set(key string, value interface{}, ttl time.Duration) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
// Wrap the value with metadata
|
|
item := &CacheEntry{
|
|
Value: value,
|
|
ExpiresAt: time.Now().Add(ttl),
|
|
Key: key,
|
|
}
|
|
|
|
// Check memory limits if enabled
|
|
if c.config.EnableMemoryLimit {
|
|
itemSize := c.strategy.EstimateSize(item)
|
|
|
|
// Evict items if necessary
|
|
for (c.currentMemoryBytes+itemSize > c.config.MaxMemoryBytes ||
|
|
len(c.items) >= c.config.MaxSize) && len(c.items) > 0 {
|
|
if !c.evictOne() {
|
|
break
|
|
}
|
|
}
|
|
|
|
c.currentMemoryBytes += itemSize
|
|
} else if len(c.items) >= c.config.MaxSize {
|
|
c.evictOne()
|
|
}
|
|
|
|
c.items[key] = item
|
|
c.strategy.OnAccess(key, item)
|
|
}
|
|
|
|
// Get retrieves a value from the cache
|
|
func (c *UnifiedCache) Get(key string) (interface{}, bool) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
item, exists := c.items[key]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
// Check if expired
|
|
entry, ok := item.(*CacheEntry)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
if c.strategy.ShouldEvict(entry, time.Now()) {
|
|
c.removeItem(key)
|
|
return nil, false
|
|
}
|
|
|
|
c.strategy.OnAccess(key, entry)
|
|
return entry.Value, true
|
|
}
|
|
|
|
// Delete removes an item from the cache
|
|
func (c *UnifiedCache) Delete(key string) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
c.removeItem(key)
|
|
}
|
|
|
|
// Cleanup removes expired entries
|
|
func (c *UnifiedCache) Cleanup() {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
now := time.Now()
|
|
toRemove := make([]string, 0)
|
|
|
|
for key, item := range c.items {
|
|
if c.strategy.ShouldEvict(item, now) {
|
|
toRemove = append(toRemove, key)
|
|
}
|
|
}
|
|
|
|
for _, key := range toRemove {
|
|
c.removeItem(key)
|
|
}
|
|
}
|
|
|
|
// Close stops the cache and cleans up resources
|
|
func (c *UnifiedCache) Close() {
|
|
if c.cleanupTask != nil {
|
|
c.cleanupTask.Stop()
|
|
c.cleanupTask = nil
|
|
}
|
|
}
|
|
|
|
// SetMaxSize updates the maximum cache size
|
|
func (c *UnifiedCache) SetMaxSize(size int) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
c.config.MaxSize = size
|
|
|
|
// Evict excess items
|
|
for len(c.items) > size && len(c.items) > 0 {
|
|
if !c.evictOne() {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetMetrics returns cache metrics
|
|
func (c *UnifiedCache) GetMetrics() map[string]interface{} {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"item_count": len(c.items),
|
|
"memory_bytes": c.currentMemoryBytes,
|
|
"max_size": c.config.MaxSize,
|
|
"strategy": c.strategy.Name(),
|
|
"has_cleanup": c.cleanupTask != nil,
|
|
}
|
|
}
|
|
|
|
// removeItem removes an item and updates memory tracking
|
|
func (c *UnifiedCache) removeItem(key string) {
|
|
if item, exists := c.items[key]; exists {
|
|
if c.config.EnableMemoryLimit {
|
|
c.currentMemoryBytes -= c.strategy.EstimateSize(item)
|
|
}
|
|
delete(c.items, key)
|
|
}
|
|
}
|
|
|
|
// evictOne evicts one item based on strategy
|
|
func (c *UnifiedCache) evictOne() bool {
|
|
// This is a simple implementation - strategies can override
|
|
for key := range c.items {
|
|
c.removeItem(key)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// startAutoCleanup starts the background cleanup task
|
|
func (c *UnifiedCache) startAutoCleanup() {
|
|
c.cleanupTask = NewBackgroundTask(
|
|
"unified-cache-cleanup",
|
|
c.config.CleanupInterval,
|
|
c.Cleanup,
|
|
c.logger,
|
|
)
|
|
c.cleanupTask.Start()
|
|
}
|
|
|
|
// CacheEntry wraps cached values with metadata
|
|
type CacheEntry struct {
|
|
Value interface{}
|
|
ExpiresAt time.Time
|
|
Key string
|
|
}
|
|
|
|
// LRUStrategy implements LRU eviction strategy
|
|
type LRUStrategy struct {
|
|
order *DoublyLinkedList
|
|
elements map[string]*ListNode
|
|
maxSize int
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
// NewLRUStrategy creates a new LRU strategy
|
|
func NewLRUStrategy(maxSize int) *LRUStrategy {
|
|
return &LRUStrategy{
|
|
order: NewDoublyLinkedList(),
|
|
elements: make(map[string]*ListNode, maxSize),
|
|
maxSize: maxSize,
|
|
}
|
|
}
|
|
|
|
// Name returns the strategy name
|
|
func (s *LRUStrategy) Name() string {
|
|
return "LRU"
|
|
}
|
|
|
|
// ShouldEvict checks if an item should be evicted
|
|
func (s *LRUStrategy) ShouldEvict(item interface{}, now time.Time) bool {
|
|
if entry, ok := item.(*CacheEntry); ok {
|
|
return now.After(entry.ExpiresAt)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// OnAccess updates LRU order when item is accessed
|
|
func (s *LRUStrategy) OnAccess(key string, item interface{}) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if node, exists := s.elements[key]; exists {
|
|
s.order.MoveToBack(node)
|
|
} else {
|
|
node := s.order.PushBack(key)
|
|
s.elements[key] = node
|
|
}
|
|
}
|
|
|
|
// EstimateSize estimates memory size of an item
|
|
func (s *LRUStrategy) EstimateSize(item interface{}) int64 {
|
|
// Basic size estimation
|
|
size := int64(80) // Base object overhead
|
|
|
|
if entry, ok := item.(*CacheEntry); ok {
|
|
size += int64(len(entry.Key))
|
|
|
|
switch v := entry.Value.(type) {
|
|
case string:
|
|
size += int64(len(v))
|
|
case []byte:
|
|
size += int64(len(v))
|
|
case map[string]interface{}:
|
|
size += int64(len(v)) * 64
|
|
for key, val := range v {
|
|
size += int64(len(key))
|
|
if str, ok := val.(string); ok {
|
|
size += int64(len(str))
|
|
} else {
|
|
size += 32
|
|
}
|
|
}
|
|
default:
|
|
size += 64
|
|
}
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
// DoublyLinkedList provides a simple doubly-linked list for LRU
|
|
type DoublyLinkedList struct {
|
|
head, tail *ListNode
|
|
size int
|
|
}
|
|
|
|
// ListNode represents a node in the doubly-linked list
|
|
type ListNode struct {
|
|
Key string
|
|
prev *ListNode
|
|
next *ListNode
|
|
}
|
|
|
|
// NewDoublyLinkedList creates a new doubly-linked list
|
|
func NewDoublyLinkedList() *DoublyLinkedList {
|
|
head := &ListNode{}
|
|
tail := &ListNode{}
|
|
head.next = tail
|
|
tail.prev = head
|
|
return &DoublyLinkedList{
|
|
head: head,
|
|
tail: tail,
|
|
size: 0,
|
|
}
|
|
}
|
|
|
|
// PushBack adds a key to the back of the list
|
|
func (l *DoublyLinkedList) PushBack(key string) *ListNode {
|
|
node := &ListNode{Key: key}
|
|
node.prev = l.tail.prev
|
|
node.next = l.tail
|
|
l.tail.prev.next = node
|
|
l.tail.prev = node
|
|
l.size++
|
|
return node
|
|
}
|
|
|
|
// MoveToBack moves a node to the back of the list
|
|
func (l *DoublyLinkedList) MoveToBack(node *ListNode) {
|
|
// Remove from current position
|
|
node.prev.next = node.next
|
|
node.next.prev = node.prev
|
|
|
|
// Add to back
|
|
node.prev = l.tail.prev
|
|
node.next = l.tail
|
|
l.tail.prev.next = node
|
|
l.tail.prev = node
|
|
}
|
|
|
|
// PopFront removes and returns the front node
|
|
func (l *DoublyLinkedList) PopFront() *ListNode {
|
|
if l.head.next == l.tail {
|
|
return nil
|
|
}
|
|
|
|
front := l.head.next
|
|
l.head.next = front.next
|
|
front.next.prev = l.head
|
|
l.size--
|
|
|
|
front.prev = nil
|
|
front.next = nil
|
|
return front
|
|
}
|
|
|
|
// CacheAdapter provides backward compatibility with existing cache interfaces
|
|
type CacheAdapter struct {
|
|
unified *UnifiedCache
|
|
}
|
|
|
|
// NewCacheAdapter creates an adapter for the unified cache
|
|
func NewCacheAdapter(unified *UnifiedCache) *CacheAdapter {
|
|
return &CacheAdapter{unified: unified}
|
|
}
|
|
|
|
// Set adapts the Set method
|
|
func (a *CacheAdapter) Set(key string, value interface{}, expiration time.Duration) {
|
|
a.unified.Set(key, value, expiration)
|
|
}
|
|
|
|
// Get adapts the Get method
|
|
func (a *CacheAdapter) Get(key string) (interface{}, bool) {
|
|
return a.unified.Get(key)
|
|
}
|
|
|
|
// Delete adapts the Delete method
|
|
func (a *CacheAdapter) Delete(key string) {
|
|
a.unified.Delete(key)
|
|
}
|
|
|
|
// Cleanup adapts the Cleanup method
|
|
func (a *CacheAdapter) Cleanup() {
|
|
a.unified.Cleanup()
|
|
}
|
|
|
|
// Close adapts the Close method
|
|
func (a *CacheAdapter) Close() {
|
|
a.unified.Close()
|
|
}
|
|
|
|
// SetMaxSize adapts the SetMaxSize method
|
|
func (a *CacheAdapter) SetMaxSize(size int) {
|
|
a.unified.SetMaxSize(size)
|
|
}
|