mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
Merge main into 0.8.0-redis: resolve conflicts
Merged the following features from main: - Dynamic Client Registration (RFC 7591) - Sharded cache and CPU spike prevention - Documentation updates (CNAME, index.html) Resolved conflicts in: - settings.go: Combined Redis config with Dynamic Client Registration config - universal_cache_singleton.go: Merged Redis backend support with consolidated cleanup Both features now work together: - Redis distributed caching with circuit breaker and health checks - Consolidated cleanup goroutine (reduces from 7 to 1) - SkipAutoCleanup flag honored for all cache types
This commit is contained in:
+161
-56
@@ -1,6 +1,7 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
// UniversalCacheManager manages all cache instances using the universal cache
|
||||
// It runs a single consolidated cleanup goroutine for all caches, reducing
|
||||
// goroutine count and CPU overhead compared to per-cache cleanup routines.
|
||||
type UniversalCacheManager struct {
|
||||
tokenCache *UniversalCache
|
||||
blacklistCache *UniversalCache
|
||||
@@ -20,6 +23,12 @@ type UniversalCacheManager struct {
|
||||
sharedBackend backends.CacheBackend // Shared backend (Redis) that should be closed by manager, not individual caches
|
||||
mu sync.RWMutex
|
||||
logger *Logger
|
||||
|
||||
// Consolidated cleanup management
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
cleanupStarted bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -34,12 +43,20 @@ func GetUniversalCacheManager(logger *Logger) *UniversalCacheManager {
|
||||
logger = GetSingletonNoOpLogger()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
universalCacheManager = &UniversalCacheManager{
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// Initialize with default in-memory backends
|
||||
initializeDefaultCaches(universalCacheManager, logger)
|
||||
|
||||
// Start single consolidated cleanup goroutine for all caches
|
||||
// This replaces 7 individual cleanup goroutines with 1
|
||||
universalCacheManager.startConsolidatedCleanup()
|
||||
})
|
||||
|
||||
return universalCacheManager
|
||||
@@ -52,8 +69,12 @@ func GetUniversalCacheManagerWithConfig(logger *Logger, redisConfig *RedisConfig
|
||||
logger = GetSingletonNoOpLogger()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
universalCacheManager = &UniversalCacheManager{
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
if redisConfig != nil && redisConfig.Enabled {
|
||||
@@ -63,6 +84,10 @@ func GetUniversalCacheManagerWithConfig(logger *Logger, redisConfig *RedisConfig
|
||||
logger.Info("Initializing cache manager with memory-only backend")
|
||||
initializeDefaultCaches(universalCacheManager, logger)
|
||||
}
|
||||
|
||||
// Start single consolidated cleanup goroutine for all caches
|
||||
// This replaces 7 individual cleanup goroutines with 1
|
||||
universalCacheManager.startConsolidatedCleanup()
|
||||
})
|
||||
|
||||
return universalCacheManager
|
||||
@@ -72,19 +97,21 @@ func GetUniversalCacheManagerWithConfig(logger *Logger, redisConfig *RedisConfig
|
||||
func initializeDefaultCaches(manager *UniversalCacheManager, logger *Logger) {
|
||||
// Initialize token cache - CRITICAL FIX: Reduced from 5000 to 1000
|
||||
manager.tokenCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000, // CRITICAL FIX: Reduced from 5000 to 1000 items
|
||||
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000, // CRITICAL FIX: Reduced from 5000 to 1000 items
|
||||
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize blacklist cache
|
||||
manager.blacklistCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize metadata cache with grace periods
|
||||
@@ -104,40 +131,45 @@ func initializeDefaultCaches(manager *UniversalCacheManager, logger *Logger) {
|
||||
"issuer",
|
||||
},
|
||||
},
|
||||
Logger: logger,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize JWK cache
|
||||
manager.jwkCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeJWK,
|
||||
MaxSize: 200,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeJWK,
|
||||
MaxSize: 200,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize session cache - CRITICAL FIX: Reduced from 10000 to 2000
|
||||
manager.sessionCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeSession,
|
||||
MaxSize: 2000, // CRITICAL FIX: Reduced from 10000 to 2000 items
|
||||
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
Logger: logger,
|
||||
Type: CacheTypeSession,
|
||||
MaxSize: 2000, // CRITICAL FIX: Reduced from 10000 to 2000 items
|
||||
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize introspection cache for OAuth 2.0 Token Introspection (RFC 7662)
|
||||
manager.introspectionCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeToken, // Use token cache type for introspection results
|
||||
MaxSize: 1000, // Cache up to 1000 introspection results
|
||||
DefaultTTL: 5 * time.Minute, // Short TTL for security (introspect frequently)
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken, // Use token cache type for introspection results
|
||||
MaxSize: 1000, // Cache up to 1000 introspection results
|
||||
DefaultTTL: 5 * time.Minute, // Short TTL for security (introspect frequently)
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize token type cache for performance optimization
|
||||
manager.tokenTypeCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeToken, // Use token cache type for token type detection
|
||||
MaxSize: 2000, // Cache up to 2000 token type detections
|
||||
DefaultTTL: 5 * time.Minute, // 5 minute TTL for token type detection
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken, // Use token cache type for token type detection
|
||||
MaxSize: 2000, // Cache up to 2000 token type detections
|
||||
DefaultTTL: 5 * time.Minute, // 5 minute TTL for token type detection
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
}
|
||||
|
||||
@@ -230,11 +262,12 @@ func initializeCachesWithRedis(manager *UniversalCacheManager, logger *Logger, r
|
||||
// Initialize token cache with backend
|
||||
manager.tokenCache = NewUniversalCacheWithBackend(
|
||||
UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
MaxMemoryBytes: 5 * 1024 * 1024,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
MaxMemoryBytes: 5 * 1024 * 1024,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
},
|
||||
createBackend(CacheTypeToken),
|
||||
)
|
||||
@@ -242,10 +275,11 @@ func initializeCachesWithRedis(manager *UniversalCacheManager, logger *Logger, r
|
||||
// Initialize blacklist cache (CRITICAL - must be consistent across replicas)
|
||||
manager.blacklistCache = NewUniversalCacheWithBackend(
|
||||
UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
},
|
||||
createBackend("blacklist"),
|
||||
)
|
||||
@@ -268,7 +302,8 @@ func initializeCachesWithRedis(manager *UniversalCacheManager, logger *Logger, r
|
||||
"issuer",
|
||||
},
|
||||
},
|
||||
Logger: logger,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
},
|
||||
createBackend(CacheTypeMetadata),
|
||||
)
|
||||
@@ -276,45 +311,106 @@ func initializeCachesWithRedis(manager *UniversalCacheManager, logger *Logger, r
|
||||
// Initialize JWK cache
|
||||
manager.jwkCache = NewUniversalCacheWithBackend(
|
||||
UniversalCacheConfig{
|
||||
Type: CacheTypeJWK,
|
||||
MaxSize: 200,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeJWK,
|
||||
MaxSize: 200,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
},
|
||||
createBackend(CacheTypeJWK),
|
||||
)
|
||||
|
||||
// Session cache stays memory-only (high volume, local state)
|
||||
manager.sessionCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeSession,
|
||||
MaxSize: 2000,
|
||||
MaxMemoryBytes: 5 * 1024 * 1024,
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
Logger: logger,
|
||||
Type: CacheTypeSession,
|
||||
MaxSize: 2000,
|
||||
MaxMemoryBytes: 5 * 1024 * 1024,
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Introspection cache uses backend for sharing results
|
||||
manager.introspectionCache = NewUniversalCacheWithBackend(
|
||||
UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 5 * time.Minute,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 5 * time.Minute,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
},
|
||||
createBackend(CacheTypeToken),
|
||||
)
|
||||
|
||||
// Token type cache stays memory-only (local optimization)
|
||||
manager.tokenTypeCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 2000,
|
||||
DefaultTTL: 5 * time.Minute,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 2000,
|
||||
DefaultTTL: 5 * time.Minute,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
logger.Infof("Cache manager initialized with %s backend configuration", redisConfig.CacheMode)
|
||||
}
|
||||
|
||||
// startConsolidatedCleanup starts a single cleanup goroutine for all caches
|
||||
// This reduces goroutine count from 7 to 1 and consolidates cleanup operations
|
||||
func (m *UniversalCacheManager) startConsolidatedCleanup() {
|
||||
m.mu.Lock()
|
||||
if m.cleanupStarted {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
m.cleanupStarted = true
|
||||
m.mu.Unlock()
|
||||
|
||||
m.wg.Add(1)
|
||||
go func() {
|
||||
defer m.wg.Done()
|
||||
|
||||
// Use 5-minute interval for consolidated cleanup
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
m.performConsolidatedCleanup()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
m.logger.Info("UniversalCacheManager: Started consolidated cleanup routine for all caches")
|
||||
}
|
||||
|
||||
// performConsolidatedCleanup runs cleanup on all caches in sequence
|
||||
// This is more efficient than parallel cleanup as it reduces lock contention
|
||||
func (m *UniversalCacheManager) performConsolidatedCleanup() {
|
||||
m.mu.RLock()
|
||||
caches := []*UniversalCache{
|
||||
m.tokenCache,
|
||||
m.blacklistCache,
|
||||
m.metadataCache,
|
||||
m.jwkCache,
|
||||
m.sessionCache,
|
||||
m.introspectionCache,
|
||||
m.tokenTypeCache,
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
for _, cache := range caches {
|
||||
if cache != nil {
|
||||
// Each cache.Cleanup() is self-contained and handles its own locking
|
||||
cache.Cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
m.logger.Debugf("UniversalCacheManager: Consolidated cleanup completed for all caches")
|
||||
}
|
||||
|
||||
// GetTokenCache returns the token cache
|
||||
func (m *UniversalCacheManager) GetTokenCache() *UniversalCache {
|
||||
m.mu.RLock()
|
||||
@@ -364,8 +460,16 @@ func (m *UniversalCacheManager) GetTokenTypeCache() *UniversalCache {
|
||||
return m.tokenTypeCache
|
||||
}
|
||||
|
||||
// Close shuts down all caches
|
||||
// Close shuts down all caches and the consolidated cleanup routine
|
||||
func (m *UniversalCacheManager) Close() error {
|
||||
// Stop the consolidated cleanup routine first
|
||||
if m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
|
||||
// Wait for cleanup routine to finish
|
||||
m.wg.Wait()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -387,7 +491,8 @@ func (m *UniversalCacheManager) Close() error {
|
||||
}
|
||||
}
|
||||
|
||||
m.logger.Info("UniversalCacheManager: Closed all caches")
|
||||
m.cleanupStarted = false
|
||||
m.logger.Info("UniversalCacheManager: Closed all caches and cleanup routine")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user