using System; using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Npgsql; /// /// Provides lookup for a pool based on a connection string. /// /// /// is lock-free, to avoid contention, but the same isn't /// true of , which acquires a lock. The calling code always tries /// before trying to . /// static class PoolManager { internal const int InitialPoolsSize = 10; static readonly object Lock = new(); static volatile (string Key, ConnectorSource Pool)[] _pools = new (string, ConnectorSource)[InitialPoolsSize]; static volatile int _nextSlot; internal static (string Key, ConnectorSource Pool)[] Pools => _pools; internal static bool TryGetValue(string key, [NotNullWhen(true)] out ConnectorSource? pool) { // Note that pools never get removed. _pools is strictly append-only. var nextSlot = _nextSlot; var pools = _pools; var sw = new SpinWait(); // First scan the pools and do reference equality on the connection strings for (var i = 0; i < nextSlot; i++) { var cp = pools[i]; if (ReferenceEquals(cp.Key, key)) { // It's possible that this pool entry is currently being written: the connection string // component has already been written, but the pool component is just about to be. So we // loop on the pool until it's non-null while (Volatile.Read(ref cp.Pool) == null) sw.SpinOnce(); pool = cp.Pool; return true; } } // Next try value comparison on the strings for (var i = 0; i < nextSlot; i++) { var cp = pools[i]; if (cp.Key == key) { // See comment above while (Volatile.Read(ref cp.Pool) == null) sw.SpinOnce(); pool = cp.Pool; return true; } } pool = null; return false; } internal static ConnectorSource GetOrAdd(string key, ConnectorSource pool) { lock (Lock) { if (TryGetValue(key, out var result)) return result; // May need to grow the array. if (_nextSlot == _pools.Length) { var newPools = new (string, ConnectorSource)[_pools.Length * 2]; Array.Copy(_pools, newPools, _pools.Length); _pools = newPools; } _pools[_nextSlot].Key = key; _pools[_nextSlot].Pool = pool; Interlocked.Increment(ref _nextSlot); return pool; } } internal static void Clear(string connString) { if (TryGetValue(connString, out var pool)) pool.Clear(); } internal static void ClearAll() { lock (Lock) { var pools = _pools; for (var i = 0; i < _nextSlot; i++) { var cp = pools[i]; if (cp.Key == null) return; cp.Pool?.Clear(); } } } static PoolManager() { // When the appdomain gets unloaded (e.g. web app redeployment) attempt to nicely // close idle connectors to prevent errors in PostgreSQL logs (#491). AppDomain.CurrentDomain.DomainUnload += (sender, args) => ClearAll(); AppDomain.CurrentDomain.ProcessExit += (sender, args) => ClearAll(); } /// /// Resets the pool manager to its initial state, for test purposes only. /// Assumes that no other threads are accessing the pool. /// internal static void Reset() { lock (Lock) { ClearAll(); _pools = new (string, ConnectorSource)[InitialPoolsSize]; _nextSlot = 0; } } }