// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Diagnostics; using StackExchange.Redis; namespace EpicGames.Redis { /// /// A connection pool for Redis client /// /// Wraps multiple ConnectionMultiplexers as lazy values and initializes them as needed. /// If full, the least loaded connection will be picked. /// public sealed class RedisConnectionPool : IDisposable { private readonly Lazy[] _connections; private readonly int _defaultDatabaseIndex; /// /// Constructor /// /// Size of the pool (i.e max number of connections) /// Configuration string for Redis /// The Redis database index to use. Use -1 for the default one public RedisConnectionPool(int poolSize, string redisConfString, int defaultDatabaseIndex = -1) { _defaultDatabaseIndex = defaultDatabaseIndex; _connections = new Lazy[poolSize]; for (int i = 0; i < poolSize; i++) { static void ConfigureOptions(ConfigurationOptions options) { if (Debugger.IsAttached) { options.SyncTimeout = 1_000_000; } } _connections[i] = new Lazy(() => ConnectionMultiplexer.Connect(redisConfString, ConfigureOptions)); } } /// public void Dispose() { for (int idx = 0; idx < _connections.Length; idx++) { if (_connections[idx].IsValueCreated) { _connections[idx].Value.Dispose(); } } } /// /// Get a connection from the pool /// /// It will pick the least loaded connection or create a new one (if pool size allows) /// /// A Redis database connection public IConnectionMultiplexer GetConnection() { long existingConnectionLoad = 0; IConnectionMultiplexer? existingConnection = null; Lazy? newConnection = null; // Find the least loaded connection foreach (Lazy connection in _connections) { if (connection.IsValueCreated) { ServerCounters counters = connection.Value.GetCounters(); if (existingConnection == null || counters.TotalOutstanding < existingConnectionLoad) { existingConnection = connection.Value; existingConnectionLoad = counters.TotalOutstanding; } } else { newConnection ??= connection; } } // Check if we should try to create a new connection if (existingConnection == null || existingConnectionLoad >= 10) { if (newConnection != null) { return newConnection.Value; } } // Otherwise return the best connection we already have Debug.Assert(existingConnection != null); return existingConnection; } /// /// Shortcut to getting a IDatabase /// /// A Redis database public IDatabase GetDatabase() { return GetConnection().GetDatabase(_defaultDatabaseIndex); } } }