public async Task CustomLockTimeout()
        {
            RedisHelper.FlushDatabase();

            var extension         = new RedisLockExtension(RedisHelper.GetConnection(), new RedisLockOptions(lockTimeout: TimeSpan.FromDays(1)));
            var refreshWaiterTask = new TaskCompletionSource <bool>();
            var lockWaiterTask    = new TaskCompletionSource <bool>();

            var refreshTask = extension.RefreshValueAsync("TestLock", async() =>
            {
                lockWaiterTask.SetResult(true);
                await refreshWaiterTask.Task;
                return(new CacheEntry <int>(5, TimeSpan.FromDays(1)));
            }, new CacheSettings(TimeSpan.FromHours(3)));

            await lockWaiterTask.Task;

            var database      = RedisHelper.GetConnection().GetDatabase();
            var keyWithExpiry = await database.StringGetWithExpiryAsync("Lock:TestLock");

            refreshWaiterTask.SetResult(true);

            var lockTimeout  = TimeSpan.FromDays(1);
            var actualExpiry = keyWithExpiry.Expiry.Value;

            //Due to the logistics of the wait etc, we can't do an exact comparison
            //Instead, we can safely say the above code should be completed within a minute
            //With that in mind, remove that from the set lock timeout and compare the expiry
            //is greater than this new "comparison timeout".
            var comparisonTimeout = lockTimeout - TimeSpan.FromMinutes(1);

            Assert.IsTrue(comparisonTimeout < keyWithExpiry.Expiry.Value);
        }
        public async Task RefreshValueNotifiesChannelSubscribers()
        {
            RedisHelper.FlushDatabase();

            var connection = RedisHelper.GetConnection();

            var cacheStackMock = new Mock <ICacheStack>();
            var extension      = new RedisLockExtension(connection, RedisLockOptions.Default);

            extension.Register(cacheStackMock.Object);

            var completionSource = new TaskCompletionSource <bool>();

            await connection.GetSubscriber().SubscribeAsync("CacheTower.CacheLock", (channel, value) =>
            {
                if (value == "TestKey")
                {
                    completionSource.SetResult(true);
                }
                else
                {
                    completionSource.SetResult(false);
                }
            });

            var cacheEntry = new CacheEntry <int>(13, TimeSpan.FromDays(1));

            await extension.RefreshValueAsync("TestKey",
                                              () => new ValueTask <CacheEntry <int> >(cacheEntry), new CacheSettings(TimeSpan.FromDays(1)));

            var succeedingTask = await Task.WhenAny(completionSource.Task, Task.Delay(TimeSpan.FromSeconds(10)));

            Assert.AreEqual(completionSource.Task, succeedingTask, "Subscriber response took too long");
            Assert.IsTrue(completionSource.Task.Result, "Subscribers were not notified about the refreshed value");
        }
Example #3
0
        protected override void SetupBenchmark()
        {
            base.SetupBenchmark();

            CacheExtension = new RedisLockExtension(RedisHelper.GetConnection());
            RedisHelper.FlushDatabase();
        }
        public void ThrowForRegisteringTwoCacheStacks()
        {
            var extension  = new RedisLockExtension(RedisHelper.GetConnection());
            var cacheStack = new Mock <ICacheStack>().Object;

            extension.Register(cacheStack);
            extension.Register(cacheStack);
        }
Example #5
0
        public async Task ObservedLockMultiple()
        {
            RedisHelper.FlushDatabase();

            var connection = RedisHelper.GetConnection();

            var cacheStackMock = new Mock <ICacheStack>();
            var extension      = new RedisLockExtension(connection, RedisLockOptions.Default);

            extension.Register(cacheStackMock.Object);

            var cacheEntry = new CacheEntry <int>(13, TimeSpan.FromDays(1));

            //Establish lock
            await connection.GetDatabase().StringSetAsync("Lock:TestKey", RedisValue.EmptyString);

            var refreshTask1 = extension.WithRefreshAsync("TestKey",
                                                          () =>
            {
                return(new ValueTask <CacheEntry <int> >(cacheEntry));
            },
                                                          new CacheSettings(TimeSpan.FromDays(1))
                                                          ).AsTask();

            var refreshTask2 = extension.WithRefreshAsync("TestKey",
                                                          () =>
            {
                return(new ValueTask <CacheEntry <int> >(cacheEntry));
            },
                                                          new CacheSettings(TimeSpan.FromDays(1))
                                                          ).AsTask();

            //Delay to allow for Redis check and self-entry into lock
            await Task.Delay(TimeSpan.FromSeconds(2));

            Assert.IsTrue(extension.LockedOnKeyRefresh.ContainsKey("TestKey"), "Lock was not established");

            //Trigger the end of the lock
            await connection.GetSubscriber().PublishAsync("CacheTower.CacheLock", "TestKey");

            var whenAllRefreshesTask = Task.WhenAll(refreshTask1, refreshTask2);
            var succeedingTask       = await Task.WhenAny(whenAllRefreshesTask, Task.Delay(TimeSpan.FromSeconds(10)));

            if (!succeedingTask.Equals(whenAllRefreshesTask))
            {
                RedisHelper.DebugInfo(connection);
                Assert.Fail("Refresh has timed out - something has gone very wrong");
            }

            cacheStackMock.Verify(c => c.GetAsync <int>("TestKey"), Times.Exactly(4), "Two checks to the cache stack are expected");
        }
Example #6
0
        public async Task WaitingTaskInDifferentInstanceUnlocksAndCompletes()
        {
            var connection = RedisHelper.GetConnection();

            var cacheStackMockOne = new Mock <ICacheStack>();
            var extensionOne      = new RedisLockExtension(connection);

            extensionOne.Register(cacheStackMockOne.Object);

            var cacheStackMockTwo = new Mock <ICacheStack>();
            var extensionTwo      = new RedisLockExtension(connection);

            extensionTwo.Register(cacheStackMockTwo.Object);

            var cacheEntry           = new CacheEntry <int>(13, TimeSpan.FromDays(1));
            var secondaryTaskKickoff = new TaskCompletionSource <bool>();

            var primaryTask = extensionOne.RefreshValueAsync("TestKey",
                                                             async() =>
            {
                secondaryTaskKickoff.SetResult(true);
                await Task.Delay(3000);
                return(cacheEntry);
            },
                                                             new CacheSettings(TimeSpan.FromDays(1))
                                                             ).AsTask();

            await secondaryTaskKickoff.Task;

            var secondaryTask = extensionTwo.RefreshValueAsync("TestKey",
                                                               () =>
            {
                return(new ValueTask <CacheEntry <int> >(cacheEntry));
            },
                                                               new CacheSettings(TimeSpan.FromDays(1))
                                                               ).AsTask();

            var succeedingTask = await Task.WhenAny(primaryTask, secondaryTask);

            Assert.AreEqual(await primaryTask, await succeedingTask, "Processing task call didn't complete first - something has gone very wrong.");

            //Let the secondary task finish before we verify ICacheStack method calls
            await secondaryTask;

            cacheStackMockOne.Verify(c => c.GetAsync <int>("TestKey"), Times.Never, "Processing task shouldn't be querying existing values");
            cacheStackMockTwo.Verify(c => c.GetAsync <int>("TestKey"), Times.Exactly(2), "Missed GetAsync for retrieving the updated value - this means the registered stack returned the updated value early");
        }
Example #7
0
        public async Task RefreshValueNotifiesChannelSubscribers()
        {
            var connection           = RedisHelper.GetConnection();
            var taskCompletionSource = new TaskCompletionSource <bool>();

            await connection.GetSubscriber().SubscribeAsync("CacheTower.CacheLock", (channel, value) =>
            {
                if (value == "TestKey")
                {
                    taskCompletionSource.SetResult(true);
                }

                Assert.Fail($"Unexpected value '{value}'");
                taskCompletionSource.SetResult(false);
            });

            var cacheStackMock = new Mock <ICacheStack>();
            var extension      = new RedisLockExtension(connection);

            extension.Register(cacheStackMock.Object);

            var cacheEntry = new CacheEntry <int>(13, TimeSpan.FromDays(1));

            await extension.RefreshValueAsync("TestKey",
                                              () => new ValueTask <CacheEntry <int> >(cacheEntry), new CacheSettings(TimeSpan.FromDays(1)));

            var waitTask = taskCompletionSource.Task;
            await Task.WhenAny(waitTask, Task.Delay(TimeSpan.FromSeconds(10)));

            if (!waitTask.IsCompleted)
            {
                Assert.Fail("Subscriber response took too long");
            }

            Assert.IsTrue(waitTask.Result, "Subscribers were not notified about the refreshed value");
        }