mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-13 04:07:37 +00:00
Add redis support for distributed caching (#83)
* Add redis support for distributed caching * Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * ... and another all nighter. * fixup! ... and another all nighter. * fixup! fixup! ... and another all nighter. * fixup! fixup! fixup! ... and another all nighter. * Resolve issue #85 by adding ability to set custom claims in JWT tokens * Remove redundant validation in auth middleware ( issue #89 ) * Add ability to set cookie prefix for session cookies ( #87 ) * fixup! Add ability to set cookie prefix for session cookies ( #87 ) * Add ability to set cookie max age - issue #91 * Potential fix for code scanning alert no. 10: Size computation for allocation may overflow Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fixup! Merge main into 0.8.0-redis: resolve conflicts --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent
5fcbd54955
commit
e64fc7f730
@@ -3,10 +3,13 @@ package traefikoidc
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lukaszraczylo/traefikoidc/internal/cache/backends"
|
||||
)
|
||||
|
||||
// CacheType defines the type of cache for optimized behavior
|
||||
@@ -94,6 +97,10 @@ type UniversalCache struct {
|
||||
config UniversalCacheConfig
|
||||
logger *Logger
|
||||
|
||||
// Backend for distributed caching (NEW)
|
||||
backend backends.CacheBackend
|
||||
ownsBackend bool // If true, cache should close backend on Close(); if false, backend is shared
|
||||
|
||||
// Memory management
|
||||
currentSize int64
|
||||
currentMemory int64
|
||||
@@ -115,6 +122,14 @@ func NewUniversalCache(config UniversalCacheConfig) *UniversalCache {
|
||||
return createUniversalCache(config)
|
||||
}
|
||||
|
||||
// NewUniversalCacheWithBackend creates a new universal cache with a specific backend
|
||||
func NewUniversalCacheWithBackend(config UniversalCacheConfig, cacheBackend backends.CacheBackend) *UniversalCache {
|
||||
cache := createUniversalCache(config)
|
||||
cache.backend = cacheBackend
|
||||
cache.ownsBackend = false // Shared backend, managed externally
|
||||
return cache
|
||||
}
|
||||
|
||||
// createUniversalCache is the internal constructor
|
||||
func createUniversalCache(config UniversalCacheConfig) *UniversalCache {
|
||||
// Apply type-specific defaults first (including MaxSize)
|
||||
@@ -232,6 +247,25 @@ func (c *UniversalCache) Set(key string, value interface{}, ttl time.Duration) e
|
||||
ttl = c.config.DefaultTTL
|
||||
}
|
||||
|
||||
// If we have a backend, use it for distributed caching
|
||||
if c.backend != nil {
|
||||
// Serialize the value
|
||||
data, err := c.serialize(value)
|
||||
if err != nil {
|
||||
c.logger.Errorf("Failed to serialize value for key %s: %v", key, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Store in backend
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
if err := c.backend.Set(ctx, c.prefixKey(key), data, ttl); err != nil {
|
||||
c.logger.Infof("Backend set error for key %s: %v", key, err)
|
||||
// Continue with local cache even if backend fails
|
||||
}
|
||||
}
|
||||
|
||||
size := c.estimateSize(value)
|
||||
|
||||
c.mu.Lock()
|
||||
@@ -294,6 +328,32 @@ func (c *UniversalCache) Set(key string, value interface{}, ttl time.Duration) e
|
||||
|
||||
// Get retrieves a value from the cache
|
||||
func (c *UniversalCache) Get(key string) (interface{}, bool) {
|
||||
// Try backend first if available (for distributed consistency)
|
||||
if c.backend != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
data, _, exists, err := c.backend.Get(ctx, c.prefixKey(key))
|
||||
if err != nil {
|
||||
c.logger.Debugf("Backend get error for key %s: %v", key, err)
|
||||
// Fall through to local cache
|
||||
} else if exists {
|
||||
// Deserialize the value
|
||||
var value interface{}
|
||||
if err := c.deserialize(data, &value); err != nil {
|
||||
c.logger.Errorf("Failed to deserialize value for key %s: %v", key, err)
|
||||
// Fall through to local cache
|
||||
} else {
|
||||
atomic.AddInt64(&c.hits, 1)
|
||||
// Update local cache with backend value
|
||||
go func() {
|
||||
_ = c.updateLocalCache(key, value, c.config.DefaultTTL)
|
||||
}()
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -350,6 +410,17 @@ func (c *UniversalCache) Get(key string) (interface{}, bool) {
|
||||
|
||||
// Delete removes a key from the cache
|
||||
func (c *UniversalCache) Delete(key string) bool {
|
||||
// Delete from backend if available
|
||||
if c.backend != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
if _, err := c.backend.Delete(ctx, c.prefixKey(key)); err != nil {
|
||||
c.logger.Debugf("Backend delete error for key %s: %v", key, err)
|
||||
// Continue with local delete
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -364,6 +435,17 @@ func (c *UniversalCache) Delete(key string) bool {
|
||||
|
||||
// Clear removes all items from the cache
|
||||
func (c *UniversalCache) Clear() {
|
||||
// Clear backend if available
|
||||
if c.backend != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := c.backend.Clear(ctx); err != nil {
|
||||
c.logger.Infof("Backend clear error: %v", err)
|
||||
// Continue with local clear
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -446,6 +528,13 @@ func (c *UniversalCache) Close() error {
|
||||
// Clear all items
|
||||
c.Clear()
|
||||
|
||||
// Close backend only if this cache owns it (not shared)
|
||||
if c.backend != nil && c.ownsBackend {
|
||||
if err := c.backend.Close(); err != nil {
|
||||
c.logger.Infof("Failed to close cache backend: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.logger.Debugf("UniversalCache[%s]: Closed", c.config.Type)
|
||||
return nil
|
||||
}
|
||||
@@ -710,3 +799,60 @@ func (c *UniversalCache) Mutex() *sync.RWMutex {
|
||||
func (c *UniversalCache) Strategy() CacheStrategy {
|
||||
return c.config.Strategy
|
||||
}
|
||||
|
||||
// serialize converts a value to bytes for backend storage
|
||||
func (c *UniversalCache) serialize(value interface{}) ([]byte, error) {
|
||||
// Use JSON for serialization - simple and universal
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
// deserialize converts bytes from backend storage to a value
|
||||
func (c *UniversalCache) deserialize(data []byte, value interface{}) error {
|
||||
// Use JSON for deserialization
|
||||
return json.Unmarshal(data, value)
|
||||
}
|
||||
|
||||
// prefixKey adds a cache type prefix to the key for backend storage
|
||||
func (c *UniversalCache) prefixKey(key string) string {
|
||||
return fmt.Sprintf("%s:%s", c.config.Type, key)
|
||||
}
|
||||
|
||||
// updateLocalCache updates the local cache with a value from the backend
|
||||
func (c *UniversalCache) updateLocalCache(key string, value interface{}, ttl time.Duration) error {
|
||||
size := c.estimateSize(value)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Check memory limits
|
||||
if c.config.MaxMemoryBytes > 0 {
|
||||
for c.currentMemory+size > c.config.MaxMemoryBytes && c.lruList.Len() > 0 {
|
||||
c.evictOldest()
|
||||
}
|
||||
}
|
||||
|
||||
// Check size limits
|
||||
if c.lruList.Len() >= c.config.MaxSize {
|
||||
c.evictOldest()
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
item := &CacheItem{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Size: size,
|
||||
ExpiresAt: now.Add(ttl),
|
||||
LastAccessed: now,
|
||||
AccessCount: 1,
|
||||
CacheType: c.config.Type,
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
item.element = c.lruList.PushFront(key)
|
||||
c.items[key] = item
|
||||
|
||||
c.currentSize++
|
||||
c.currentMemory += size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user