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