public async Task Properly_traps_timeouts() { var lockName = "the-lock"; var client = A.Fake <IDatabase>(); var manager = A.Fake <IConnectionMultiplexer>(); A.CallTo(() => manager.GetDatabase(A <int> .Ignored, A <object> .Ignored)).Returns(client); var exceptionThrownDuringTest = new TimeoutException("test is faking it!"); A.CallTo(() => client.LockTake( A <RedisKey> .That.Matches(str => str == lockName), A <RedisValue> .Ignored, A <TimeSpan> .Ignored, A <CommandFlags> .Ignored)).Throws(exceptionThrownDuringTest); var handle = new RedisDistributedAppLock(manager); var ex = await Assert.ThrowsAsync <DistributedAppLockException>( async() => await handle.AcquireLockAsync(lockName, TimeSpan.FromSeconds(2))); Assert.Equal(DistributedAppLockExceptionReason.Timeout, ex.Reason); var innerEx = ex.InnerException; Assert.NotNull(innerEx); Assert.Equal(exceptionThrownDuringTest, innerEx); }
public void Test_that_auto_expire_on_lock_works() { /* * This test flow goes something like: * 1. acquire firstLock that auto-expires after 2 seconds * 2. Start a new thread that will immediately sleep for 3 seconds to allow the auto-expire to kick in * 3. Try to acquire second lock which should work after 3 seconds as the lock has to be automatically released * 4. first thread unlocks after 3 seconds and then we can assert that our lock-states are correct */ RedisDistributedAppLock firstLock = null; RedisDistributedAppLock secondLock = null; var lockName = "some-other-lock-that-expire"; // ensure that no stale keys are left _connectionPool.GetConnection().GetDatabase().KeyDelete(lockName); var locker = new RedisDistributedAppLockProvider(_connectionPool); firstLock = (RedisDistributedAppLock)locker.Acquire(lockName, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)); // Create task to dispose of lock at some point Task.Factory.StartNew(() => { // delay for a bit more than the auto-expire, and we should be good to go. Task.Delay(TimeSpan.FromMilliseconds(3000)); Assert.Equal(lockName, secondLock.Name); Assert.True(secondLock.IsActive); Assert.False(firstLock.IsActive); secondLock.Dispose(); firstLock.Dispose(); }); // this second lock now enters retry mode secondLock = (RedisDistributedAppLock)locker.Acquire(lockName, TimeSpan.FromSeconds(20)); }
public async Task Does_set_lock_take_time_and_happened_immediately() { var handle = new RedisDistributedAppLock(_manager, _rng); await handle.AcquireLockAsync(_defaultLockName); Assert.True(handle.WasAcquiredInstantly); Assert.True(handle.TimeUsedToAcquire.Ticks > 0); }
public void Does_throw_if_lock_not_active() { var handle = new RedisDistributedAppLock(_manager); var ex = Assert.Throws <InvalidOperationException>(() => handle.ThrowIfNotActiveWithGivenName(_defaultLockName)); Assert.Equal( $"Lock precondition mismatch, required IsActive=true with name '{_defaultLockName}' but IsActive=false with name ''", ex.Message); }
public async Task Does_acquire_lock_with_ttl_if_passed() { var handle = new RedisDistributedAppLock(_manager); await handle.AcquireLockAsync(_defaultLockName, TimeSpan.MaxValue, TimeSpan.FromSeconds(2)); A.CallTo(() => _client.LockTake( A <RedisKey> .That.Matches(str => str == _defaultLockName), A <RedisValue> .Ignored, TimeSpan.FromSeconds(2), A <CommandFlags> .Ignored)) .MustHaveHappened(); Assert.Equal(_defaultLockName, handle.Name); }
public async Task Does_acquire_lock_using_redisclient() { var handle = new RedisDistributedAppLock(_manager, _rng); await handle.AcquireLockAsync(_defaultLockName); A.CallTo(() => _client.LockTakeAsync( A <RedisKey> .That.Matches(str => str == _defaultLockName), A <RedisValue> .Ignored, A <TimeSpan> .Ignored, A <CommandFlags> .Ignored)) .MustHaveHappened(); Assert.Equal(_defaultLockName, handle.Name); }
public async Task Does_dispose_of_underlying_resources() { var handle = new RedisDistributedAppLock(_manager); using (await handle.AcquireLockAsync(_defaultLockName, TimeSpan.FromSeconds(2))) { // empty on purpose } // after using scope underlying lock must be released A.CallTo(() => _client.LockRelease( A <RedisKey> .That.Matches(str => str == _defaultLockName), A <RedisValue> .Ignored, A <CommandFlags> .Ignored)).MustHaveHappened(); }
public void Test_acquire_lock_with_timeout_works() { /* * This test flow goes something like: * 1. acquire firstLock * 2. Start a new thread that immediately returns and waits for a while * 3. Try to acquire second lock, but be patient and wait for up to 20 secs while retrying * 4. thread unlocks first lock after 700 ms * 5. thread waits 500 ms * 6. now we expect the second call to acquire to have succeeded, assert that this is true */ RedisDistributedAppLock firstLock = null; RedisDistributedAppLock secondLock = null; var lockName = "some-other-lock"; // ensure that no stale keys are left _connectionPool.GetConnection().GetDatabase().KeyDelete(lockName); var locker = new RedisDistributedAppLockProvider(_connectionPool); firstLock = (RedisDistributedAppLock)locker.Acquire(lockName, TimeSpan.FromSeconds(1)); Assert.True(firstLock.WasAcquiredInstantly); // Create task to dispose of loclk at some point Task.Factory.StartNew(() => { Task.Delay(TimeSpan.FromMilliseconds(700)); // release the first lock firstLock.Dispose(); // wait and the new lock should be acquired Task.Delay(TimeSpan.FromMilliseconds(500)); Assert.Equal(lockName, secondLock.Name); Assert.True(secondLock.IsActive); Assert.False((secondLock.WasAcquiredInstantly)); Assert.True(secondLock.TimeUsedToAcquire.Ticks > 0); secondLock.Dispose(); }); // this second lock now enters retry mode secondLock = (RedisDistributedAppLock)locker.Acquire(lockName, TimeSpan.FromSeconds(20)); }
public async Task Properly_traps_other_exceptions() { var lockName = "the-lock"; var client = A.Fake <IDatabase>(); var manager = A.Fake <IConnectionMultiplexer>(); A.CallTo(() => manager.GetDatabase(A <int> .Ignored, A <object> .Ignored)).Returns(client); A.CallTo(() => client.LockTakeAsync( A <RedisKey> .That.Matches(str => str == lockName), A <RedisValue> .Ignored, A <TimeSpan> .Ignored, A <CommandFlags> .Ignored)).Throws(new OperationCanceledException("test is faking it!")); var handle = new RedisDistributedAppLock(manager, _rng); var ex = await Assert.ThrowsAsync <DistributedAppLockException>( async() => await handle.AcquireLockAsync(lockName, TimeSpan.FromSeconds(2))); Assert.Equal(DistributedAppLockExceptionReason.SeeInnerException, ex.Reason); Assert.IsType <OperationCanceledException>(ex.InnerException); }
public void Does_throw_if_lock_name_does_not_match() { var handle = new RedisDistributedAppLock(_manager); var ex = Assert.Throws <InvalidOperationException>(() => handle.ThrowIfNotActiveWithGivenName("this-is-the-wrong-name")); }