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