using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StackExchange.Redis; using System.Text.Json; namespace StellaOps.Provcache.Valkey; public sealed partial class ValkeyProvcacheStore : IProvcacheStore, IAsyncDisposable { private const int DefaultScanPageSize = 200; private const int DefaultDeleteBatchSize = 500; private readonly IConnectionMultiplexer _connectionMultiplexer; private readonly ProvcacheOptions _options; private readonly ILogger _logger; private readonly TimeProvider _timeProvider; private readonly JsonSerializerOptions _jsonOptions; private readonly SemaphoreSlim _connectionLock = new(1, 1); private IDatabase? _database; public string ProviderName => "valkey"; public ValkeyProvcacheStore( IConnectionMultiplexer connectionMultiplexer, IOptions options, ILogger logger, TimeProvider? timeProvider = null) { _connectionMultiplexer = connectionMultiplexer ?? throw new ArgumentNullException(nameof(connectionMultiplexer)); _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _timeProvider = timeProvider ?? TimeProvider.System; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }; } private string BuildKey(string veriKey) => $"{_options.ValkeyKeyPrefix}{veriKey}"; private async Task GetDatabaseAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_database is not null) { return _database; } await _connectionLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { _database ??= _connectionMultiplexer.GetDatabase(); return _database; } finally { _connectionLock.Release(); } } public async ValueTask DisposeAsync() { _connectionLock.Dispose(); // Note: Don't dispose the connection multiplexer if it's shared (injected via DI) // The DI container will handle its lifetime await Task.CompletedTask; } }