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);
        }