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"); }
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"); }
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"); }