Example #1
0
        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);
        }
Example #2
0
        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));
        }
Example #3
0
        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);
        }
Example #4
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);
        }
Example #5
0
        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);
        }
Example #6
0
        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);
        }
Example #7
0
        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();
        }
Example #8
0
        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));
        }
Example #9
0
        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);
        }
Example #10
0
 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"));
 }