private async ValueTask StoreOrDisposeLockAsync(string connectionString, MultiplexedConnectionLock @lock, bool shouldDispose) { if (shouldDispose) { try { await @lock.DisposeAsync().ConfigureAwait(false); } catch { /* swallow */ } } using (await this._lock.AcquireAsync(CancellationToken.None).ConfigureAwait(false)) { ++this._storeCountSinceLastPrune; if (shouldDispose) { // If we're about to dispose the lock, check if it has an empty pool that can be removed from our dictionary. // By itself this doesn't guarantee cleanup: after a successful acquire we'll have an empty lock left over that won't // go away unless we use THAT connection string again. To help with this, we have pruning if (this._poolsByConnectionString.TryGetValue(connectionString, out var pool) && pool.Count == 0) { this._poolsByConnectionString.Remove(connectionString); } } else // otherwise, store the lock { ++this._pooledLockCount; if (this._poolsByConnectionString.TryGetValue(connectionString, out var existing)) { existing.Enqueue(@lock); } else { var newPool = new Queue <MultiplexedConnectionLock>(); newPool.Enqueue(@lock); this._poolsByConnectionString.Add(connectionString, newPool); } } if (this.IsDueForPruningNoLock()) { await this.PrunePoolsNoLockAsync().ConfigureAwait(false); } } }
public Handle(MultiplexedConnectionLock @lock, IDbSynchronizationStrategy <TLockCookie> strategy, string name, TLockCookie lockCookie) { this._name = name; this._box = RefBox.Create((@lock, strategy, lockCookie, default(IDatabaseConnectionMonitoringHandle))); }
public async ValueTask <IDistributedSynchronizationHandle?> TryAcquireAsync <TLockCookie>( string connectionString, string name, TimeoutValue timeout, IDbSynchronizationStrategy <TLockCookie> strategy, TimeoutValue keepaliveCadence, CancellationToken cancellationToken) where TLockCookie : class { // opportunistic phase: see if we can use a connection that is already holding a lock // to acquire the current lock var existingLock = await this.GetExistingLockOrDefaultAsync(connectionString).ConfigureAwait(false); if (existingLock != null) { var canSafelyDisposeExistingLock = false; try { var opportunisticResult = await TryAcquireAsync(existingLock, opportunistic : true).ConfigureAwait(false); if (opportunisticResult.Handle != null) { return(opportunisticResult.Handle); } // this will always be false if handle is non-null, so we can set if afterwards canSafelyDisposeExistingLock = opportunisticResult.CanSafelyDispose; switch (opportunisticResult.Retry) { case MultiplexedConnectionLockRetry.NoRetry: return(null); case MultiplexedConnectionLockRetry.RetryOnThisLock: var retryOnThisLockResult = await TryAcquireAsync(existingLock, opportunistic : false).ConfigureAwait(false); canSafelyDisposeExistingLock = retryOnThisLockResult.CanSafelyDispose; return(retryOnThisLockResult.Handle); case MultiplexedConnectionLockRetry.Retry: break; default: throw new InvalidOperationException("unexpected retry"); } } finally { // since we took this lock from the pool, always return it to the pool await this.StoreOrDisposeLockAsync(connectionString, existingLock, shouldDispose : canSafelyDisposeExistingLock).ConfigureAwait(false); } } // normal phase: if we were not able to be opportunistic, ensure that we have a lock var @lock = new MultiplexedConnectionLock(this.ConnectionFactory(connectionString)); MultiplexedConnectionLock.Result?result = null; try { result = await TryAcquireAsync(@lock, opportunistic : false).ConfigureAwait(false); Invariant.Require(result !.Value.Retry == MultiplexedConnectionLockRetry.NoRetry, "Acquire on fresh lock should not recommend a retry"); } finally { // if we failed to even acquire a result on a brand new lock, then there's definitely no reason to store it await this.StoreOrDisposeLockAsync(connectionString, @lock, shouldDispose : result?.CanSafelyDispose ?? true).ConfigureAwait(false); } return(result.Value.Handle); ValueTask <MultiplexedConnectionLock.Result> TryAcquireAsync(MultiplexedConnectionLock @lock, bool opportunistic) => @lock.TryAcquireAsync(name, timeout, strategy, keepaliveCadence, cancellationToken, opportunistic); }