Example #1
0
        public async ValueTask <IDistributedSynchronizationHandle?> TryAcquireAsync <TLockCookie>(
            TimeoutValue timeout,
            IDbSynchronizationStrategy <TLockCookie> strategy,
            CancellationToken cancellationToken,
            IDistributedSynchronizationHandle?contextHandle)
            where TLockCookie : class
        {
            IDistributedSynchronizationHandle?result = null;
            IAsyncDisposable?connectionResource      = null;

            try
            {
                DatabaseConnection connection;
                if (contextHandle != null)
                {
                    connection = GetContextHandleConnection <TLockCookie>(contextHandle);
                }
                else
                {
                    connectionResource = connection = this._connectionFactory();
                    if (connection.IsExernallyOwned)
                    {
                        if (!connection.CanExecuteQueries)
                        {
                            throw new InvalidOperationException("The connection and/or transaction are disposed or closed");
                        }
                    }
                    else
                    {
                        await connection.OpenAsync(cancellationToken).ConfigureAwait(false);

                        if (this._transactionScopedIfPossible) // for an internally-owned connection, we must create the transaction
                        {
                            await connection.BeginTransactionAsync().ConfigureAwait(false);
                        }
                    }
                }

                var lockCookie = await strategy.TryAcquireAsync(connection, this._name, timeout, cancellationToken).ConfigureAwait(false);

                if (lockCookie != null)
                {
                    result = new Handle <TLockCookie>(connection, strategy, this._name, lockCookie, transactionScoped: this._transactionScopedIfPossible && connection.HasTransaction, connectionResource);
                    if (!this._keepaliveCadence.IsInfinite)
                    {
                        connection.SetKeepaliveCadence(this._keepaliveCadence);
                    }
                }
            }
            finally
            {
                // if we fail to acquire or throw, make sure to clean up the connection
                if (result == null && connectionResource != null)
                {
                    await connectionResource.DisposeAsync().ConfigureAwait(false);
                }
            }

            return(result);
        }
        public async ValueTask <Result> TryAcquireAsync <TLockCookie>(
            string name,
            TimeoutValue timeout,
            IDbSynchronizationStrategy <TLockCookie> strategy,
            TimeoutValue keepaliveCadence,
            CancellationToken cancellationToken,
            bool opportunistic)
            where TLockCookie : class
        {
            using var mutextHandle = await this._mutex.TryAcquireAsync(opportunistic?TimeSpan.Zero : Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false);

            if (mutextHandle == null)
            {
                // mutex wasn't free, so just give up
                Invariant.Require(opportunistic);
                // The current lock is busy so we allow retry but on a different lock instance. We can't safely dispose
                // since we never acquired the mutex so we can't check _heldLocks
                return(new Result(MultiplexedConnectionLockRetry.Retry, canSafelyDispose: false));
            }

            try
            {
                if (this._heldLocksToKeepaliveCadences.ContainsKey(name))
                {
                    // we won't try to hold the same lock twice on one connection. At some point, we could
                    // support this case in-memory using a counter for each multiply-held lock name and being careful
                    // with modes
                    return(this.GetFailureResultNoLock(isAlreadyHeld: true, opportunistic, timeout));
                }

                if (!this._connection.CanExecuteQueries)
                {
                    await this._connection.OpenAsync(cancellationToken).ConfigureAwait(false);
                }

                var lockCookie = await strategy.TryAcquireAsync(this._connection, name, opportunistic?TimeSpan.Zero : timeout, cancellationToken).ConfigureAwait(false);

                if (lockCookie != null)
                {
                    var handle = new ManagedFinalizationDistributedLockHandle(new Handle <TLockCookie>(this, strategy, name, lockCookie));
                    this._heldLocksToKeepaliveCadences.Add(name, keepaliveCadence);
                    if (!keepaliveCadence.IsInfinite)
                    {
                        this.SetKeepaliveCadenceNoLock();
                    }
                    return(new Result(handle));
                }

                // we failed to acquire the lock, so we should retry if we were being opportunistic and artificially
                // shortened the timeout
                return(this.GetFailureResultNoLock(isAlreadyHeld: false, opportunistic, timeout));
            }
            finally
            {
                await this.CloseConnectionIfNeededNoLockAsync().ConfigureAwait(false);
            }
        }
 public Handle(
     DatabaseConnection connection,
     IDbSynchronizationStrategy <TLockCookie> strategy,
     string name,
     TLockCookie lockCookie,
     bool transactionScoped,
     IAsyncDisposable?connectionResource)
 {
     this._innerHandle = new InnerHandle(connection, strategy, name, lockCookie, transactionScoped, connectionResource);
     // we don't do managed finalization for externally-owned connections/transactions since it might violate thread-safety
     // on those objects (we don't know when they're in use)
     this._finalizer = connection.IsExernallyOwned ? null : ManagedFinalizerQueue.Instance.Register(this, this._innerHandle);
 }
 public InnerHandle(
     DatabaseConnection connection,
     IDbSynchronizationStrategy <TLockCookie> strategy,
     string name,
     TLockCookie lockCookie,
     bool scopedToOwnTransaction,
     IAsyncDisposable?connectionResource)
 {
     this.Connection  = connection;
     this._strategy   = strategy;
     this._name       = name;
     this._lockCookie = lockCookie;
     this._scopedToOwnedTransaction = scopedToOwnTransaction;
     this._connectionResource       = connectionResource;
 }
        public ValueTask <IDistributedSynchronizationHandle?> TryAcquireAsync <TLockCookie>(
            TimeoutValue timeout,
            IDbSynchronizationStrategy <TLockCookie> strategy,
            CancellationToken cancellationToken,
            IDistributedSynchronizationHandle?contextHandle)
            where TLockCookie : class
        {
            // cannot multiplex for updates, since we cannot predict whether or not there will be a request to elevate
            // to an exclusive lock which asks for a long timeout
            if (!strategy.IsUpgradeable && contextHandle == null)
            {
                return(this._multiplexedConnectionLockPool.TryAcquireAsync(this._connectionString, this._name, timeout, strategy, keepaliveCadence: this._keepaliveCadence, cancellationToken));
            }

            // otherwise, fall back to our fallback lock
            return(this._fallbackLock.TryAcquireAsync(timeout, strategy, cancellationToken, contextHandle));
        }
        private async ValueTask ReleaseAsync <TLockCookie>(IDbSynchronizationStrategy <TLockCookie> strategy, string name, TLockCookie lockCookie)
            where TLockCookie : class
        {
            using var _ = await this._mutex.AcquireAsync(CancellationToken.None).ConfigureAwait(false);

            try
            {
                await strategy.ReleaseAsync(this._connection, name, lockCookie).ConfigureAwait(false);
            }
            finally
            {
                if (this._heldLocksToKeepaliveCadences.TryGetValue(name, out var keepaliveCadence))
                {
                    this._heldLocksToKeepaliveCadences.Remove(name);
                    if (!keepaliveCadence.IsInfinite)
                    {
                        // note: we do this even if we're about to close the connection because we'll want
                        // the correct cadence set when and if we re-open
                        this.SetKeepaliveCadenceNoLock();
                    }
                }
                await this.CloseConnectionIfNeededNoLockAsync().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);
        }