using System.Net; using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Options; using StackExchange.Redis; namespace Nop.Services.Caching; /// /// Redis connection wrapper /// /// /// This class should be registered on IoC as singleton instance /// public partial class RedisConnectionWrapper : IRedisConnectionWrapper { #region Fields protected readonly SemaphoreSlim _connectionLock = new(1, 1); protected volatile IConnectionMultiplexer _connection; protected readonly RedisCacheOptions _options; #endregion #region Ctor public RedisConnectionWrapper(IOptions optionsAccessor) { _options = optionsAccessor.Value; } #endregion #region Utilities /// /// Create a new ConnectionMultiplexer instance /// /// protected virtual async Task ConnectAsync() { IConnectionMultiplexer connection; if (_options.ConnectionMultiplexerFactory is null) { if (_options.ConfigurationOptions is not null) connection = await ConnectionMultiplexer.ConnectAsync(_options.ConfigurationOptions); else connection = await ConnectionMultiplexer.ConnectAsync(_options.Configuration); } else { connection = await _options.ConnectionMultiplexerFactory(); } if (_options.ProfilingSession != null) connection.RegisterProfiler(_options.ProfilingSession); return connection; } /// /// Create a new ConnectionMultiplexer instance /// /// protected virtual IConnectionMultiplexer Connect() { IConnectionMultiplexer connection; if (_options.ConnectionMultiplexerFactory is null) connection = _options.ConfigurationOptions is not null ? ConnectionMultiplexer.Connect(_options.ConfigurationOptions) : ConnectionMultiplexer.Connect(_options.Configuration); else connection = _options.ConnectionMultiplexerFactory().GetAwaiter().GetResult(); if (_options.ProfilingSession != null) connection.RegisterProfiler(_options.ProfilingSession); return connection; } /// /// Get connection to Redis servers, and reconnects if necessary /// /// protected virtual async Task GetConnectionAsync() { if (_connection?.IsConnected == true) return _connection; await _connectionLock.WaitAsync(); try { if (_connection?.IsConnected == true) return _connection; //Connection disconnected. Disposing connection... _connection?.Dispose(); //Creating new instance of Redis Connection _connection = await ConnectAsync(); } finally { _connectionLock.Release(); } return _connection; } /// /// Get connection to Redis servers, and reconnects if necessary /// /// protected virtual IConnectionMultiplexer GetConnection() { if (_connection?.IsConnected == true) return _connection; _connectionLock.Wait(); try { if (_connection?.IsConnected == true) return _connection; //Connection disconnected. Disposing connection... _connection?.Dispose(); //Creating new instance of Redis Connection _connection = Connect(); } finally { _connectionLock.Release(); } return _connection; } #endregion #region Methods /// /// Obtain an interactive connection to a database inside Redis /// /// Redis cache database public async Task GetDatabaseAsync() { return (await GetConnectionAsync()).GetDatabase(); } /// /// Obtain an interactive connection to a database inside Redis /// /// Redis cache database public IDatabase GetDatabase() { return GetConnection().GetDatabase(); } /// /// Obtain a configuration API for an individual server /// /// The network endpoint /// Redis server public async Task GetServerAsync(EndPoint endPoint) { return (await GetConnectionAsync()).GetServer(endPoint); } /// /// Gets all endpoints defined on the server /// /// Array of endpoints public async Task GetEndPointsAsync() { return (await GetConnectionAsync()).GetEndPoints(); } /// /// Gets a subscriber for the server /// /// Array of endpoints public async Task GetSubscriberAsync() { return (await GetConnectionAsync()).GetSubscriber(); } /// /// Gets a subscriber for the server /// /// Array of endpoints public ISubscriber GetSubscriber() { return GetConnection().GetSubscriber(); } /// /// Delete all the keys of the database /// public async Task FlushDatabaseAsync() { var endPoints = await GetEndPointsAsync(); await Task.WhenAll(endPoints.Select(async endPoint => { var server = await GetServerAsync(endPoint); if (!server.IsReplica) { await server.FlushDatabaseAsync(); } })); } /// /// Release all resources associated with this object /// public void Dispose() { //dispose ConnectionMultiplexer _connection?.Dispose(); } #endregion #region Properties /// /// The Redis instance name /// public string Instance => _options.InstanceName ?? string.Empty; #endregion }