public LockScope(DbConnection connection, ISqlSynchronizationStrategy <TLockCookie> strategy, string lockName, TLockCookie lockCookie)
 {
     this.connection = connection;
     this.strategy   = strategy;
     this.lockName   = lockName;
     this.lockCookie = lockCookie;
 }
        public IDisposable?TryAcquire <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, IDisposable?contextHandle)
            where TLockCookie : class
        {
            if (contextHandle != null)
            {
                return(this.CreateContextLock <TLockCookie>(contextHandle).TryAcquire(timeoutMillis, strategy, contextHandle: null));
            }

            IDisposable?result     = null;
            var         connection = SqlHelpers.CreateConnection(this.connectionString);

            try
            {
                connection.Open();
                var lockCookie = strategy.TryAcquire(connection, this.lockName, timeoutMillis);
                if (lockCookie != null)
                {
                    result = new LockScope <TLockCookie>(connection, strategy, this.lockName, lockCookie);
                }
            }
            finally
            {
                // if we fail to acquire or throw, make sure to clean up the connection
                if (result == null)
                {
                    connection.Dispose();
                }
            }

            return(result);
        }
        public IDisposable TryAcquire <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, IDisposable contextHandle)
            where TLockCookie : class
        {
            if (contextHandle != null)
            {
                return(this.CreateContextLock(contextHandle).TryAcquire(timeoutMillis, strategy, contextHandle: null));
            }

            IDisposable    result      = null;
            var            connection  = new SqlConnection(this.connectionString);
            SqlTransaction transaction = null;

            try
            {
                connection.Open();
                // when creating a transaction, the isolation level doesn't matter, since we're using sp_getapplock
                transaction = connection.BeginTransaction();
                var lockCookie = strategy.TryAcquire(transaction, this.lockName, timeoutMillis);
                if (lockCookie != null)
                {
                    result = new LockScope(transaction);
                }
            }
            finally
            {
                // if we fail to acquire or throw, make sure to clean up
                if (result == null)
                {
                    transaction?.Dispose();
                    connection.Dispose();
                }
            }

            return(result);
        }
Esempio n. 4
0
        public IDisposable?TryAcquire <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, IDisposable?contextHandle)
            where TLockCookie : class
        {
            this.CheckConnection();

            var lockCookie = strategy.TryAcquire(this.connectionOrTransaction, this.lockName, timeoutMillis);

            return(this.CreateHandle(strategy, lockCookie));
        }
Esempio n. 5
0
        private Result CreateSuccessResult <TLockCookie>(ISqlSynchronizationStrategy <TLockCookie> strategy, string lockName, TLockCookie lockCookie)
            where TLockCookie : class
        {
            var nonThreadSafeHandle = new ReleaseAction(() => this.ReleaseNoLock(strategy, lockName, lockCookie));
            var threadSafeHandle    = new ThreadSafeReleaseAction(this.mutex, nonThreadSafeHandle);

            this.outstandingHandles.Add(lockName, new HandleReference(threadSafeHandle: threadSafeHandle, nonThreadSafeHandle: nonThreadSafeHandle));
            return(new Result(threadSafeHandle));
        }
            public void Dispose()
            {
                var connection = Interlocked.Exchange(ref this.connection, null);

                if (connection != null && !connection.IsClosedOrBroken())
                {
                    ReleaseLock(connection, this.strategy !, this.lockName, this.lockCookie !);
                    this.strategy   = null;
                    this.lockCookie = null;
                }
            }
Esempio n. 7
0
        public Task <IDisposable> TryAcquireAsync <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDisposable 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(MultiplexedConnectionLockPool.Instance.TryAcquireAsync(connectionString, lockName, timeoutMillis, strategy, cancellationToken));
            }

            // otherwise, fall back to our fallback lock
            return(this.fallbackLock.TryAcquireAsync(timeoutMillis, strategy, cancellationToken, contextHandle));
        }
Esempio n. 8
0
 private void ReleaseNoLock <TLockCookie>(ISqlSynchronizationStrategy <TLockCookie> strategy, string lockName, TLockCookie lockCookie)
     where TLockCookie : class
 {
     try
     {
         strategy.Release(this.connection, lockName, lockCookie);
     }
     finally
     {
         this.outstandingHandles.Remove(lockName);
         this.CloseConnectionIfNeededNoLock();
     }
 }
 private static void ReleaseLock(DbConnection connection, ISqlSynchronizationStrategy <TLockCookie> strategy, string lockName, TLockCookie lockCookie)
 {
     try
     {
         // explicit release is required due to connection pooling. For a pooled connection,
         // simply calling Dispose() will not release the lock: it just returns the connection
         // to the pool
         strategy.Release(connection, lockName, lockCookie);
     }
     finally
     {
         connection.Dispose();
     }
 }
Esempio n. 10
0
        public IDisposable TryAcquire <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, IDisposable contextHandle)
            where TLockCookie : class
        {
            if (contextHandle != null)
            {
                // if we are taking a nested lock, we don't want to start another keepalive on the same connection.
                // However, we do need to stop our current keepalive while we take the nested lock to avoid threading issues
                var lockScope = (LockScope)contextHandle;
                lockScope.Keepalive.Stop();
                try
                {
                    var internalHandle = lockScope.InternalLock.TryAcquire(timeoutMillis, strategy, contextHandle: lockScope.InternalHandle);
                    return(internalHandle != null
                        ? new LockScope(internalHandle, lockScope.InternalLock, lockScope.Keepalive, connection: null)
                        : null);
                }
                finally
                {
                    // always restart, even if the acquisition fails
                    lockScope.Keepalive.Start();
                }
            }

            var       connection = new SqlConnection(this.connectionString);
            LockScope result     = null;

            try
            {
                connection.Open();
                var internalLock   = new ExternalConnectionOrTransactionSqlDistributedLock(this.lockName, connection);
                var internalHandle = internalLock.TryAcquire(timeoutMillis, strategy, contextHandle: null);
                if (internalHandle != null)
                {
                    var keepalive = new KeepaliveHelper(connection);
                    keepalive.Start();
                    result = new LockScope(internalHandle, internalLock, keepalive, connection);
                }
            }
            finally
            {
                if (result == null)
                {
                    connection.Dispose();
                }
            }

            return(result);
        }
Esempio n. 11
0
        public async Task <Result> TryAcquireAsync <TLockCookie>(
            string lockName,
            int timeoutMillis,
            ISqlSynchronizationStrategy <TLockCookie> strategy,
            CancellationToken cancellationToken,
            bool opportunistic)
            where TLockCookie : class
        {
            if (!await this.mutex.WaitAsync(opportunistic ? TimeSpan.Zero : Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false))
            {
                // mutex wasn't free, so just give up
                return(this.GetFailureResultNoLock(Reason.MutexTimeout, opportunistic, timeoutMillis));
            }
            try
            {
                if (this.outstandingHandles.ContainsKey(lockName))
                {
                    // 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(Reason.AlreadyHeld, opportunistic, timeoutMillis));
                }

                if (this.connection.State != ConnectionState.Open)
                {
                    await this.connection.OpenAsync(cancellationToken).ConfigureAwait(false);
                }

                var lockCookie = await strategy.TryAcquireAsync(this.connection, lockName, opportunistic? 0 : timeoutMillis, cancellationToken).ConfigureAwait(false);

                if (lockCookie != null)
                {
                    return(this.CreateSuccessResult(strategy, lockName, lockCookie));
                }

                // we failed to acquire the lock, so we should retry if we were being opportunistic and artificially
                // shortened the timeout
                return(this.GetFailureResultNoLock(Reason.AcquireTimeout, opportunistic, timeoutMillis));
            }
            finally
            {
                this.CloseConnectionIfNeededNoLock();
                this.mutex.Release();
            }
        }
Esempio n. 12
0
        private IDisposable?CreateHandle <TLockCookie>(ISqlSynchronizationStrategy <TLockCookie> strategy, TLockCookie?lockCookie) where TLockCookie : class
        {
            if (lockCookie == null)
            {
                return(null);
            }

            return(new ReleaseAction(() =>
            {
                if (this.connectionOrTransaction.Connection?.IsClosedOrBroken() ?? true)
                {
                    // lost the connection or transaction disposed, so the lock was already released
                    return;
                }

                strategy.Release(this.connectionOrTransaction, this.lockName, lockCookie);
            }));
        }
Esempio n. 13
0
        public Result TryAcquire <TLockCookie>(
            string lockName,
            int timeoutMillis,
            ISqlSynchronizationStrategy <TLockCookie> strategy,
            bool opportunistic)
            where TLockCookie : class
        {
            if (!this.mutex.Wait(opportunistic ? TimeSpan.Zero : Timeout.InfiniteTimeSpan))
            {
                // mutex wasn't free, so just give up
                return(this.GetFailureResultNoLock(Reason.MutexTimeout, opportunistic, timeoutMillis));
            }
            try
            {
                if (this.outstandingHandles.ContainsKey(lockName))
                {
                    // 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(Reason.AlreadyHeld, opportunistic, timeoutMillis));
                }

                if (this.connection.State != ConnectionState.Open)
                {
                    this.connection.Open();
                }

                var lockCookie = strategy.TryAcquire(this.connection, lockName, opportunistic ? 0 : timeoutMillis);
                if (lockCookie != null)
                {
                    return(this.CreateSuccessResult(strategy, lockName, lockCookie));
                }

                return(this.GetFailureResultNoLock(Reason.AcquireTimeout, opportunistic, timeoutMillis));
            }
            finally
            {
                this.CloseConnectionIfNeededNoLock();
                this.mutex.Release();
            }
        }
Esempio n. 14
0
        public async Task <IDisposable?> TryAcquireAsync <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDisposable?contextHandle)
            where TLockCookie : class
        {
            this.CheckConnection();

            var lockCookie = await strategy.TryAcquireAsync(this.connectionOrTransaction, this.lockName, timeoutMillis, cancellationToken).ConfigureAwait(false);

            return(this.CreateHandle(strategy, lockCookie));
        }
Esempio n. 15
0
        public async Task <IDisposable> TryAcquireAsync <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDisposable contextHandle)
            where TLockCookie : class
        {
            if (contextHandle != null)
            {
                cancellationToken.ThrowIfCancellationRequested(); // if already canceled, exit immediately

                // if we are taking a nested lock, we don't want to start another keepalive on the same connection.
                // However, we do need to stop our current keepalive while we take the nested lock to avoid threading issues
                var lockScope = (LockScope)contextHandle;
                await lockScope.Keepalive.StopAsync().ConfigureAwait(false);

                try
                {
                    var internalHandle = await lockScope.InternalLock.TryAcquireAsync(timeoutMillis, strategy, cancellationToken, contextHandle : lockScope.InternalHandle).ConfigureAwait(false);

                    return(internalHandle != null
                        ? new LockScope(internalHandle, lockScope.InternalLock, lockScope.Keepalive, connection: null)
                        : null);
                }
                finally
                {
                    // always restart, even if the acquisition fails
                    lockScope.Keepalive.Start();
                }
            }

            var       connection = new SqlConnection(this.connectionString);
            LockScope result     = null;

            try
            {
                await connection.OpenAsync(cancellationToken).ConfigureAwait(false);

                var internalLock   = new ExternalConnectionOrTransactionSqlDistributedLock(this.lockName, connection);
                var internalHandle = await internalLock.TryAcquireAsync(timeoutMillis, strategy, cancellationToken, contextHandle : null).ConfigureAwait(false);

                if (internalHandle != null)
                {
                    var keepalive = new KeepaliveHelper(connection);
                    keepalive.Start();
                    result = new LockScope(internalHandle, internalLock, keepalive, connection);
                }
            }
            finally
            {
                if (result == null)
                {
                    connection.Dispose();
                }
            }

            return(result);
        }
        public async Task <IDisposable?> TryAcquireAsync <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDisposable?contextHandle)
            where TLockCookie : class
        {
            if (contextHandle != null)
            {
                return(await this.CreateContextLock <TLockCookie>(contextHandle).TryAcquireAsync(timeoutMillis, strategy, cancellationToken, contextHandle: null).ConfigureAwait(false));
            }

            IDisposable?result     = null;
            var         connection = SqlHelpers.CreateConnection(this.connectionString);

            try
            {
                await connection.OpenAsync(cancellationToken).ConfigureAwait(false);

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

                if (lockCookie != null)
                {
                    result = new LockScope <TLockCookie>(connection, strategy, this.lockName, lockCookie);
                }
            }
            finally
            {
                // if we fail to acquire or throw, make sure to clean up the connection
                if (result == null)
                {
                    connection.Dispose();
                }
            }

            return(result);
        }
        public IDisposable TryAcquire <TLockCookie>(
            string connectionString,
            string lockName,
            int timeoutMillis,
            ISqlSynchronizationStrategy <TLockCookie> strategy)
            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 = this.GetExistingLockOrDefault(connectionString);

            if (existingLock != null)
            {
                try
                {
                    var opportunisticResult = existingLock.TryAcquire(lockName, timeoutMillis, strategy, opportunistic: true);
                    if (opportunisticResult.Handle != null)
                    {
                        return(opportunisticResult.Handle);
                    }

                    switch (opportunisticResult.Retry)
                    {
                    case MultiplexedConnectionLockRetry.NoRetry:
                        return(null);

                    case MultiplexedConnectionLockRetry.RetryOnThisLock:
                        var result = existingLock.TryAcquire(lockName, timeoutMillis, strategy, opportunistic: false);
                        return(result.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
                    this.AddLockToPool(connectionString, existingLock);
                }
            }

            // normal phase: if we were not able to be opportunistic, ensure that we have a lock
            var         @lock  = new MultiplexedConnectionLock(connectionString);
            IDisposable handle = null;

            try
            {
                handle = @lock.TryAcquire(lockName, timeoutMillis, strategy, opportunistic: false).Handle;
            }
            finally
            {
                // only store the lock on success; otherwise there's no reason to keep it around
                if (handle != null)
                {
                    this.AddLockToPool(connectionString, @lock);
                }
                else
                {
                    @lock.Dispose();
                }
            }
            return(handle);
        }
        public async Task <IDisposable> TryAcquireAsync <TLockCookie>(int timeoutMillis, ISqlSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDisposable contextHandle = null)
            where TLockCookie : class
        {
            if (contextHandle != null)
            {
                return(await this.CreateContextLock(contextHandle).TryAcquireAsync(timeoutMillis, strategy, cancellationToken, contextHandle: null).ConfigureAwait(false));
            }

            IDisposable    result      = null;
            var            connection  = new SqlConnection(this.connectionString);
            SqlTransaction transaction = null;

            try
            {
                await connection.OpenAsync(cancellationToken).ConfigureAwait(false);

                // when creating a transaction, the isolation level doesn't matter, since we're using sp_getapplock
                transaction = connection.BeginTransaction();
                var lockCookie = await strategy.TryAcquireAsync(transaction, this.lockName, timeoutMillis, cancellationToken).ConfigureAwait(false);

                if (lockCookie != null)
                {
                    result = new LockScope(transaction);
                }
            }
            finally
            {
                // if we fail to acquire or throw, make sure to clean up
                if (result == null)
                {
                    transaction?.Dispose();
                    connection.Dispose();
                }
            }

            return(result);
        }