Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/FeatureFlags/Caching/CacheService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,21 @@ public interface ICacheService
/// In-memory implementation of cache service using concurrent dictionary.
/// Suitable for single-server deployments. For distributed scenarios, use DistributedCacheService.
/// </summary>
{public sealed class InMemoryCacheService {
{public sealed class InMemoryCacheService : IDisposable {
private readonly ConcurrentDictionary<string, CacheEntry> _cache;
private readonly ILogger<InMemoryCacheService> _logger;
private readonly TimeSpan _defaultTtl;
private readonly CancellationTokenSource _cleanupCts;

public InMemoryCacheService(ILogger<InMemoryCacheService> logger, TimeSpan? defaultTtl = null)
{
_cache = new ConcurrentDictionary<string, CacheEntry>();
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_defaultTtl = defaultTtl ?? TimeSpan.FromMinutes(5);
_cleanupCts = new CancellationTokenSource();

// Start cleanup task
_ = StartCleanupTaskAsync();
// Start cleanup task with cancellation support
_ = StartCleanupTaskAsync(_cleanupCts.Token);
}

public T? Get<T>(string key)
Expand Down Expand Up @@ -127,13 +129,13 @@ public async Task ClearAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Periodically removes expired cache entries to prevent memory bloat.
/// </summary>
private async Task StartCleanupTaskAsync()
private async Task StartCleanupTaskAsync(CancellationToken stoppingToken)
{
while (true)
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(TimeSpan.FromMinutes(1));
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);

var expiredKeys = _cache
.Where(kvp => kvp.Value.ExpiresAt.HasValue && kvp.Value.ExpiresAt < DateTime.UtcNow)
Expand All @@ -154,13 +156,24 @@ private async Task StartCleanupTaskAsync()
_logger.LogDebug("Cache cleanup: removed {Count} expired entries", removedCount);
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Cache cleanup task stopping due to cancellation");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cache cleanup error");
}
}
}

public void Dispose()
{
_cleanupCts.Cancel();
_cleanupCts.Dispose();
}

private class CacheEntry
{
public object? Value { get; set; }
Expand Down
Loading