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");
        }
        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 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.RefreshValueAsync("TestKey",
                                                           () =>
            {
                return(new ValueTask <CacheEntry <int> >(cacheEntry));
            },
                                                           new CacheSettings(TimeSpan.FromDays(1))
                                                           ).AsTask();

            var refreshTask2 = extension.RefreshValueAsync("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)));

            Assert.AreEqual(whenAllRefreshesTask, succeedingTask, "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");
        }
Exemple #4
0
        public async Task WaitingTaskInSameInstanceUnlocksAndCompletes()
        {
            var connection = RedisHelper.GetConnection();

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

            extension.Register(cacheStackMock.Object);

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

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

            await secondaryTaskKickoff.Task;

            var secondaryTask = extension.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;

            cacheStackMock.Verify(c => c.GetAsync <int>("TestKey"), Times.Exactly(2), "Missed checks whether waiting was required or retrieving the updated value");
        }
Exemple #5
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");
        }