/// <summary>
        /// Acquire the lock with the underlying Redis instance
        /// </summary>
        /// <param name="lockName">Name of the lock to acquire</param>
        /// <param name="lockWaitTimeout">When set, the amount of time to wait for the lock to become available</param>
        /// <param name="lockAutoExpireTimeout">The amount of time the lock is allowed to stay in Redis before redis will auto expire the lock key</param>
        /// <returns><c>this</c> upon successful lock grab (for a fluent interface)</returns>
        /// <exception cref="DistributedAppLockException"></exception>
        public async Task <IDistributedAppLock> AcquireLockAsync(string lockName, TimeSpan?lockWaitTimeout = null,
                                                                 TimeSpan?lockAutoExpireTimeout            = null)
        {
            this.WasAcquiredInstantly = true;
            var stopWatch = new Stopwatch();

            try
            {
                LockHandle = Guid.NewGuid().ToString("N");
                var timeoutProvided = lockWaitTimeout.HasValue;
                if (!timeoutProvided)
                {
                    lockWaitTimeout = TimeSpan.Zero;
                }

                if (lockAutoExpireTimeout == null)
                {
                    lockAutoExpireTimeout = TimeSpan.FromDays(1);
                }

                _redisDb = _redisMuxer.GetDatabase();
                stopWatch.Start();
                do
                {
                    var lockWasAcquired = await _redisDb.LockTakeAsync(lockName, LockHandle, lockAutoExpireTimeout.Value);

                    if (!lockWasAcquired && !timeoutProvided)
                    {
                        throw new DistributedAppLockException("Unable to acquire lock")
                              {
                                  Reason = DistributedAppLockExceptionReason.LockAlreadyAcquired,
                              };
                    }

                    if (!lockWasAcquired)
                    {
                        this.WasAcquiredInstantly = false;
                        var timeout = _rng.Next(
                            1,
                            (int)Math.Min(2500, lockWaitTimeout.Value.TotalMilliseconds)
                            ); // wait between 1 ms and either  2,5 seconds OR lockwaitTimeout if this is smaller than 2.5 secondss
                        await Task.Delay(TimeSpan.FromMilliseconds(timeout));

                        continue;
                    }

                    // Lock was acquired, we're happy
                    IsActive = true;
                    Name     = lockName;
                    break;
                } while (stopWatch.Elapsed.TotalSeconds < lockWaitTimeout.Value.TotalSeconds);

                if (!IsActive)
                {
                    throw new DistributedAppLockException("Timeout while acquiring lock")
                          {
                              Reason = DistributedAppLockExceptionReason.Timeout,
                          };
                }

                TimeUsedToAcquire = stopWatch.Elapsed;
                return(this);
            }
            catch (TimeoutException tex)
            {
                var ex = new DistributedAppLockException($"Unable to acquire lock: '{lockName}'", tex)
                {
                    Reason = lockWaitTimeout == null
                        ? DistributedAppLockExceptionReason.LockAlreadyAcquired
                        : DistributedAppLockExceptionReason.Timeout,
                };
                throw ex;
            }
            catch (DistributedAppLockException)
            {
                throw; // simply rethrow these types of exceptions to avoid being trapped in the generic Exception catcher
            }
            catch (Exception rex)
            {
                var ex = new DistributedAppLockException($"Unable to acquire lock: '{lockName}'", rex)
                {
                    Reason = DistributedAppLockExceptionReason.SeeInnerException,
                };
                throw ex;
            }
            finally
            {
                stopWatch?.Stop();
            }
        }
예제 #2
0
        public async Task <IDistributedAppLock> AcquireLockAsync(
            string lockName,
            TimeSpan?lockWaitTimeout       = null,
            TimeSpan?lockAutoExpireTimeout = null)
        {
            try
            {
                _dbConnection = _dbConnectionFactory();

                if (_dbConnection.State != ConnectionState.Open)
                {
                    throw new ArgumentException(
                              "The IDbConnection returned by the factory function must be Open. Call the Open() method on the connection before returning",
                              "dbConnection");
                }

                var timeoutProvided = lockWaitTimeout.HasValue;
                if (!timeoutProvided)
                {
                    lockWaitTimeout = TimeSpan.Zero;
                }

                if (lockAutoExpireTimeout.HasValue)
                {
                    throw new NotSupportedException("Sql AppLock does not support auto expiry.");
                }

                var parameters = new DynamicParameters();
                parameters.Add("@Resource", lockName);
                parameters.Add("@LockMode", "Exclusive");
                parameters.Add("@DbPrincipal", "public");
                parameters.Add("@LockOwner", "Session");
                parameters.Add("@LockTimeout", lockWaitTimeout.Value.TotalMilliseconds);
                parameters.Add("exitCode", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);

                await _dbConnection.ExecuteAsync("sp_getapplock",
                                                 parameters,
                                                 commandType : CommandType.StoredProcedure);

                var exitCode = parameters.Get <int>("exitCode");

                if (exitCode < (int)SpGetAppLockReturnCode.Granted)
                {
                    // acquire lock failure
                    if (exitCode == (int)SpGetAppLockReturnCode.LockRequestTimeout && lockWaitTimeout != TimeSpan.Zero)
                    {
                        throw new DistributedAppLockException($"Timeout while acquiring lock: '{lockName}'")
                              {
                                  Reason = DistributedAppLockExceptionReason.Timeout
                              };
                    }

                    throw new DistributedAppLockException($"Unable to acquire lock: '{lockName}'")
                          {
                              Reason = DistributedAppLockExceptionReason.LockAlreadyAcquired
                          };
                }

                // lock is available, whop whop!
                IsActive = true;
                Name     = lockName;
                return(this);
            }
            catch (DistributedAppLockException)
            {
                throw; // simply rethrow these types of exceptions to avoid being trapped in the generic Exception catcher
            }
            catch (Exception rex)
            {
                var ex = new DistributedAppLockException($"Unable to acquire lock: '{lockName}'", rex)
                {
                    Reason = DistributedAppLockExceptionReason.SeeInnerException,
                };
                throw ex;
            }
        }