diff --git a/IntegrationTests/RedisTests.cs b/IntegrationTests/RedisTests.cs index 0eeceb1..fbddd84 100644 --- a/IntegrationTests/RedisTests.cs +++ b/IntegrationTests/RedisTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Threading; +using System.Threading.Tasks; namespace PubComp.RedisRepo.IntegrationTests { @@ -76,7 +77,7 @@ public static void Retry(Action action, int maxAttempts) return RetryUtil.Retry(() => this.Database.KeyTimeToLive(key), 3); } } - + #endregion #region Test Cases @@ -306,6 +307,115 @@ public void SetOperations_Double() .OrderBy(x => x).ToList()); } + [TestMethod] + public async Task AsyncSetOperations_Double() + { + var key1 = TestContext.TestName + ".1"; + var key2 = TestContext.TestName + ".2"; + var key3 = TestContext.TestName + ".3"; + + redisContext.Delete(key1); + redisContext.Delete(key2); + redisContext.Delete(key3); + + await redisContext.SetAddAsync(key1, new[] { 5.0, 2.0, 1.5 }); + await redisContext.SetAddAsync(key1, 3.5); + + CollectionAssert.AreEquivalent( + new[] { 1.5, 2.0, 3.5, 5.0 }, + (await redisContext.SetGetItemsAsync(key1, RedisValueConverter.ToDouble)) + .OrderBy(x => x).ToList()); + + await redisContext.SetAddAsync(key2, new[] { 7.0, 4.0, 1.5 }); + await redisContext.SetAddAsync(key3, new[] { 1.5, 7.0, 3.5, 8.5 }); + + var actualIntersect123 = await redisContext.SetsIntersectAsync( + new[] { key1, key2, key3 }, RedisValueConverter.ToDouble); + CollectionAssert.AreEquivalent( + new[] { 1.5 }, + actualIntersect123.OrderBy(x => x).ToList()); + + var actualUnion123 = await redisContext.SetsUnionAsync( + new[] { key1, key2, key3 }, RedisValueConverter.ToDouble); + CollectionAssert.AreEquivalent( + new[] { 1.5, 2.0, 3.5, 4.0, 5.0, 7.0, 8.5 }, + actualUnion123.OrderBy(x => x).ToList()); + + var actualMinus123 = await redisContext.SetsDiffAsync( + new[] { key1, key2, key3 }, RedisValueConverter.ToDouble); + CollectionAssert.AreEquivalent( + new[] { 2.0, 5.0 }, + actualMinus123.OrderBy(x => x).ToList()); + + Assert.AreEqual(4, await redisContext.SetLengthAsync(key1)); + Assert.AreEqual(3, await redisContext.SetLengthAsync(key2)); + Assert.AreEqual(4, await redisContext.SetLengthAsync(key3)); + + await redisContext.SetRemoveAsync(key1, 2.0); + Assert.AreEqual(3, await redisContext.SetLengthAsync(key1)); + CollectionAssert.AreEquivalent( + new[] { 1.5, 3.5, 5.0 }, + (await redisContext.SetGetItemsAsync(key1, RedisValueConverter.ToDouble)) + .OrderBy(x => x).ToList()); + + await redisContext.SetRemoveAsync(key3, new[] { 2.0, 8.5, 7.0 }); + Assert.AreEqual(2, await redisContext.SetLengthAsync(key3)); + CollectionAssert.AreEquivalent( + new[] { 1.5, 3.5 }, + (await redisContext.SetGetItemsAsync(key3, RedisValueConverter.ToDouble)) + .OrderBy(x => x).ToList()); + + redisContext.Delete(key2); + await redisContext.SetAddAsync(key2, 9.0); + Assert.AreEqual(1, await redisContext.SetLengthAsync(key2)); + CollectionAssert.AreEquivalent( + new[] { 9.0 }, + (await redisContext.SetGetItemsAsync(key2, RedisValueConverter.ToDouble)) + .OrderBy(x => x).ToList()); + } + + [TestMethod] + public async Task AsyncSetOperations_Parallel() + { + var key1 = TestContext.TestName + ".1"; + + redisContext.Delete(key1); + + var range = Enumerable.Range(1, 10).ToList(); + + var tasks = range.Select(i => redisContext.SetAddAsync(key1, i)); + await Task.WhenAll(tasks); + + var set = (await redisContext.SetGetItemsAsync(key1, RedisValueConverter.ToInt)) + .OrderBy(x => x).ToList(); + + CollectionAssert.AreEquivalent(range, set); + } + + + [TestMethod] + public async Task SetOperations_NullEmptyValuesParam() + { + var key1 = TestContext.TestName + ".1"; + + redisContext.Delete(key1); + + int[] nullValues = null; + int[] emptyValues = new int[0]; + + Assert.ThrowsException(() => redisContext.SetAdd(key1, nullValues)); + await Assert.ThrowsExceptionAsync(() => redisContext.SetAddAsync(key1, nullValues)); + + Assert.AreEqual(0, redisContext.SetAdd(key1, emptyValues)); + Assert.AreEqual(0, await redisContext.SetAddAsync(key1, emptyValues)); + + Assert.ThrowsException(() => redisContext.SetRemove(key1, nullValues)); + await Assert.ThrowsExceptionAsync(() => redisContext.SetRemoveAsync(key1, nullValues)); + + Assert.AreEqual(0, redisContext.SetRemove(key1, emptyValues)); + Assert.AreEqual(0, await redisContext.SetRemoveAsync(key1, emptyValues)); + } + [TestMethod] public void SetDeleteMany() { @@ -694,18 +804,40 @@ public void TestAddToRedisSet() ValidateSetResults(key, new[] { "a", "b", "c", "bar" }); } + [TestMethod] + public async Task TestAddToRedisSetAsync() + { + const string key = "k1"; + var values = new[] { "bar", "bar", "a", "b", "c" }; + + await redisContext.AddToSetAsync(key, values); + + await ValidateSetResultsAsync(key, new[] { "a", "b", "c", "bar" }); + } + [TestMethod] public void TestSetContainsTrue() { TestSetContains("a", "a", true); } + [TestMethod] + public async Task TestSetContainsTrueAsync() + { + await TestSetContainsAsync("a", "a", true); + } + [TestMethod] public void TestSetContainsFalse() { TestSetContains("a", "b", false); } + [TestMethod] + public async Task TestSetContainsFalseAsync() + { + await TestSetContainsAsync("a", "b", false); + } [TestMethod] public void TestCountSetMembers() @@ -718,6 +850,17 @@ public void TestCountSetMembers() Assert.AreEqual(4, redisContext.CountSetMembers(key)); } + [TestMethod] + public async Task TestCountSetMembersAsync() + { + const string key = "k2"; + var values = new[] { "bar", "bar", "a", "b", "c", "a", "b" }; + + await redisContext.AddToSetAsync(key, values); + + Assert.AreEqual(4, await redisContext.CountSetMembersAsync(key)); + } + [TestMethod] public void TestSetsDiff() { @@ -734,6 +877,22 @@ public void TestSetsDiff() CollectionAssert.AreEquivalent(new[] { "c", "d", "e" }, results); } + [TestMethod] + public async Task TestSetsDiffAsync() + { + const string key1 = "testSetDiff1"; + var values1 = new[] { "a", "b", "c", "d", "e" }; + + const string key2 = "testSetDiff2"; + var values2 = new[] { "a", "b", }; + + await redisContext.AddToSetAsync(key1, values1); + await redisContext.AddToSetAsync(key2, values2); + + var results = await redisContext.GetSetsDifferenceAsync(new[] { key1, key2 }); + CollectionAssert.AreEquivalent(new[] { "c", "d", "e" }, results); + } + [TestMethod] public void TestSetsUnion() { @@ -750,6 +909,22 @@ public void TestSetsUnion() CollectionAssert.AreEquivalent(new[] { "a", "b", "c" }, results); } + [TestMethod] + public async Task TestSetsUnionAsync() + { + const string key1 = "TestSetsUnion1"; + var values1 = new[] { "a", "c" }; + + const string key2 = "TestSetsUnion2"; + var values2 = new[] { "a", "b" }; + + await redisContext.AddToSetAsync(key1, values1); + await redisContext.AddToSetAsync(key2, values2); + + var results = await redisContext.UnionSetsAsync(new[] { key1, key2 }); + CollectionAssert.AreEquivalent(new[] { "a", "b", "c" }, results); + } + [TestMethod] public void TestSetsIntersect() { @@ -766,12 +941,34 @@ public void TestSetsIntersect() CollectionAssert.AreEquivalent(new[] { "a" }, results); } + [TestMethod] + public async Task TestSetsIntersectAsync() + { + const string key1 = "TestSetsIntersect1"; + var values1 = new[] { "a", "c" }; + + const string key2 = "TestSetsIntersect2"; + var values2 = new[] { "a", "b" }; + + await redisContext.AddToSetAsync(key1, values1); + await redisContext.AddToSetAsync(key2, values2); + + var results = await redisContext.IntersectSetsAsync(new[] { key1, key2 }); + CollectionAssert.AreEquivalent(new[] { "a" }, results); + } + private void ValidateSetResults(string key, string[] expected) { var valuesFromRedis = redisContext.GetSetMembers(key); CollectionAssert.AreEquivalent(expected, valuesFromRedis); } + private async Task ValidateSetResultsAsync(string key, string[] expected) + { + var valuesFromRedis = await redisContext.GetSetMembersAsync(key); + CollectionAssert.AreEquivalent(expected, valuesFromRedis); + } + public void TestSetContains(string valueToAdd, string searchForValue, bool expected) { const string key = "testSetContains"; @@ -784,6 +981,18 @@ public void TestSetContains(string valueToAdd, string searchForValue, bool expec Assert.AreEqual(expected, setContains); } + public async Task TestSetContainsAsync(string valueToAdd, string searchForValue, bool expected) + { + const string key = "testSetContains"; + var values = new[] { "foo", "bar" }; + + await redisContext.AddToSetAsync(key, values); + await redisContext.AddToSetAsync(key, new[] { valueToAdd }); + + var setContains = await redisContext.SetContainsAsync(key, searchForValue); + Assert.AreEqual(expected, setContains); + } + #endregion #region Redis Hashes diff --git a/RedisRepo/IRedisContext.cs b/RedisRepo/IRedisContext.cs index 3d919ff..c7afe0f 100644 --- a/RedisRepo/IRedisContext.cs +++ b/RedisRepo/IRedisContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using StackExchange.Redis; namespace PubComp.RedisRepo @@ -78,63 +79,109 @@ public interface IRedisContext #region Redis Sets bool SetAdd(string key, T value); + Task SetAddAsync(string key, T value); long SetAdd(string key, T[] values); + Task SetAddAsync(string key, T[] values); bool SetRemove(string key, T value); + Task SetRemoveAsync(string key, T value); long SetRemove(string key, T[] values); + Task SetRemoveAsync(string key, T[] values); long SetLength(string key); + Task SetLengthAsync(string key); T[] SetGetItems(string key, Func redisValueConverter); + Task SetGetItemsAsync(string key, Func redisValueConverter); T[] SetsUnion(string[] keys, Func redisValueConverter); + Task SetsUnionAsync(string[] keys, Func redisValueConverter); T[] SetsIntersect(string[] keys, Func redisValueConverter); + Task SetsIntersectAsync(string[] keys, Func redisValueConverter); T[] SetsDiff(string[] keys, Func redisValueConverter); + Task SetsDiffAsync(string[] keys, Func redisValueConverter); void AddToSet(string key, string[] values); + Task AddToSetAsync(string key, string[] values); long CountSetMembers(string key); + Task CountSetMembersAsync(string key); string[] GetSetMembers(string key); + Task GetSetMembersAsync(string key); /// /// Get the diff between the set at index 0 of and all other sets in /// string[] GetSetsDifference(string[] keys); + /// + /// Get the diff between the set at index 0 of and all other sets in + /// + Task GetSetsDifferenceAsync(string[] keys); + /// /// Union sets at keys /// string[] UnionSets(string[] keys); + /// + /// Union sets at keys + /// + Task UnionSetsAsync(string[] keys); + /// /// Intersect sets at keys /// string[] IntersectSets(string[] keys); + /// + /// Intersect sets at keys + /// + Task IntersectSetsAsync(string[] keys); + /// /// Get the diff between the set at index 0 of and all other sets in /// store the result at /// void StoreSetsDifference(string destinationKey, string[] keys); + /// + /// Get the diff between the set at index 0 of and all other sets in + /// store the result at + /// + Task StoreSetsDifferenceAsync(string destinationKey, string[] keys); + /// /// Union sets at keys /// store the result at /// void UnionSetsAndStore(string destinationKey, string[] keys); + /// + /// Union sets at keys + /// store the result at + /// + Task UnionSetsAndStoreAsync(string destinationKey, string[] keys); + /// /// Intersect sets at keys /// store the result at /// void IntersectSetsAndStore(string destinationKey, string[] keys); + /// + /// Intersect sets at keys + /// store the result at + /// + Task IntersectSetsAndStoreAsync(string destinationKey, string[] keys); + bool SetContains(string key, string member); + Task SetContainsAsync(string key, string member); bool TryGetDistributedLock(string lockObjectName, string lockerName, TimeSpan lockTtl); diff --git a/RedisRepo/RedisContext.cs b/RedisRepo/RedisContext.cs index f84b674..b157b19 100644 --- a/RedisRepo/RedisContext.cs +++ b/RedisRepo/RedisContext.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace PubComp.RedisRepo { @@ -265,10 +266,18 @@ public static TResult Retry(Func func, int maxAttempts) { return RetryUtil.Retry(func, maxAttempts); } + public async static Task RetryAsync(Func> func, int maxAttempts) + { + return await RetryUtil.RetryAsync(func, maxAttempts).ConfigureAwait(false); + } public static void Retry(Action action, int maxAttempts) { RetryUtil.Retry(action, maxAttempts); } + public async static Task RetryAsync(Func action, int maxAttempts) + { + await RetryUtil.RetryAsync(action, maxAttempts).ConfigureAwait(false); + } #endregion public virtual string Key(string key) @@ -489,9 +498,30 @@ public bool SetAdd(string key, T value) return result; } + public async Task SetAddAsync(string key, T value) + { + var redisValue = value.ToRedis(); + + var result = await RetryAsync(() => + this.Database.SetAddAsync( + Key(key), redisValue, commandFlags), defaultRetries).ConfigureAwait(false); + + return result; + } + public long SetAdd(string key, T[] values) { - var redisValues = values?.Select(val => val.ToRedis()).ToArray(); + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (values.Length == 0) + { + return 0; + } + + var redisValues = values.Select(val => val.ToRedis()).ToArray(); var result = Retry(() => this.Database.SetAdd( @@ -500,6 +530,27 @@ public long SetAdd(string key, T[] values) return result; } + public async Task SetAddAsync(string key, T[] values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (values.Length == 0) + { + return 0; + } + + var redisValues = values.Select(val => val.ToRedis()).ToArray(); + + var result = await RetryAsync(() => + this.Database.SetAddAsync( + Key(key), redisValues, commandFlags), defaultRetries).ConfigureAwait(false); + + return result; + } + public T[] SetGetItems(string key, Func redisValueConverter) { var results = Retry(() => @@ -508,6 +559,14 @@ public T[] SetGetItems(string key, Func redisValueConverter) return results.Select(r => redisValueConverter(r)).ToArray(); } + public async Task SetGetItemsAsync(string key, Func redisValueConverter) + { + var results = await RetryAsync(() => + this.Database.SetMembersAsync(Key(key), commandFlags), defaultRetries).ConfigureAwait(false); + + return results.Select(r => redisValueConverter(r)).ToArray(); + } + public T[] SetsUnion(string[] keys, Func redisValueConverter) { var redisKeys = keys.Select(k => (RedisKey)Key(k)).ToArray(); @@ -519,6 +578,17 @@ public T[] SetsUnion(string[] keys, Func redisValueConverter) return results.Select(r => redisValueConverter(r)).ToArray(); } + public async Task SetsUnionAsync(string[] keys, Func redisValueConverter) + { + var redisKeys = keys.Select(k => (RedisKey)Key(k)).ToArray(); + + var results = await RetryAsync(() => + this.Database.SetCombineAsync( + SetOperation.Union, redisKeys, commandFlags), defaultRetries).ConfigureAwait(false); + + return results.Select(r => redisValueConverter(r)).ToArray(); + } + public T[] SetsIntersect(string[] keys, Func redisValueConverter) { var redisKeys = keys.Select(k => (RedisKey)Key(k)).ToArray(); @@ -530,6 +600,17 @@ public T[] SetsIntersect(string[] keys, Func redisValueConverter) return results.Select(r => redisValueConverter(r)).ToArray(); } + public async Task SetsIntersectAsync(string[] keys, Func redisValueConverter) + { + var redisKeys = keys.Select(k => (RedisKey)Key(k)).ToArray(); + + var results = await RetryAsync(() => + this.Database.SetCombineAsync( + SetOperation.Intersect, redisKeys, commandFlags), defaultRetries).ConfigureAwait(false); + + return results.Select(r => redisValueConverter(r)).ToArray(); + } + public T[] SetsDiff(string[] keys, Func redisValueConverter) { var redisKeys = keys.Select(k => (RedisKey)Key(k)).ToArray(); @@ -541,6 +622,17 @@ public T[] SetsDiff(string[] keys, Func redisValueConverter) return results.Select(r => redisValueConverter(r)).ToArray(); } + public async Task SetsDiffAsync(string[] keys, Func redisValueConverter) + { + var redisKeys = keys.Select(k => (RedisKey)Key(k)).ToArray(); + + var results = await RetryAsync(() => + this.Database.SetCombineAsync( + SetOperation.Difference, redisKeys, commandFlags), defaultRetries).ConfigureAwait(false); + + return results.Select(r => redisValueConverter(r)).ToArray(); + } + public bool SetRemove(string key, T value) { var redisValue = value.ToRedis(); @@ -552,9 +644,30 @@ public bool SetRemove(string key, T value) return result; } + public async Task SetRemoveAsync(string key, T value) + { + var redisValue = value.ToRedis(); + + var result = await RetryAsync(() => + this.Database.SetRemoveAsync( + Key(key), redisValue, commandFlags), defaultRetries).ConfigureAwait(false); + + return result; + } + public long SetRemove(string key, T[] values) { - var redisValues = values?.Select(val => val.ToRedis()).ToArray(); + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (values.Length == 0) + { + return 0; + } + + var redisValues = values.Select(val => val.ToRedis()).ToArray(); var result = Retry(() => this.Database.SetRemove( @@ -563,6 +676,27 @@ public long SetRemove(string key, T[] values) return result; } + public async Task SetRemoveAsync(string key, T[] values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (values.Length == 0) + { + return 0; + } + + var redisValues = values.Select(val => val.ToRedis()).ToArray(); + + var result = await RetryAsync(() => + this.Database.SetRemoveAsync( + Key(key), redisValues, commandFlags), defaultRetries).ConfigureAwait(false); + + return result; + } + public long SetLength(string key) { var result = Retry(() => @@ -571,22 +705,46 @@ public long SetLength(string key) return result; } + public async Task SetLengthAsync(string key) + { + var result = await RetryAsync(() => + this.Database.SetLengthAsync(Key(key), commandFlags), defaultRetries).ConfigureAwait(false); + + return result; + } + public void AddToSet(string key, string[] values) { Retry(() => this.Database.SetAdd(Key(key), values.ToRedisValueArray(), flags: commandFlags), defaultRetries); } + public async Task AddToSetAsync(string key, string[] values) + { + await RetryAsync(() => this.Database.SetAddAsync(Key(key), values.ToRedisValueArray(), flags: commandFlags), defaultRetries).ConfigureAwait(false); + } + public long CountSetMembers(string key) { return Retry(() => this.Database.SetLength(Key(key), flags: commandFlags), defaultRetries); } + public async Task CountSetMembersAsync(string key) + { + return await RetryAsync(() => this.Database.SetLengthAsync(Key(key), flags: commandFlags), defaultRetries).ConfigureAwait(false); + } + public string[] GetSetMembers(string key) { var results = Retry(() => this.Database.SetMembers(Key(key), flags: commandFlags), defaultRetries); return results.ToStringArray(); } + public async Task GetSetMembersAsync(string key) + { + var results = await RetryAsync(() => this.Database.SetMembersAsync(Key(key), flags: commandFlags), defaultRetries).ConfigureAwait(false); + return results.ToStringArray(); + } + /// /// Get the diff between the set at index 0 of and all other sets in /// @@ -597,6 +755,16 @@ public string[] GetSetsDifference(string[] keys) keys); } + /// + /// Get the diff between the set at index 0 of and all other sets in + /// + public async Task GetSetsDifferenceAsync(string[] keys) + { + return await OperateOnSetAsync( + SetOperation.Difference, + keys).ConfigureAwait(false); + } + /// /// Union sets at keys /// @@ -605,6 +773,14 @@ public string[] UnionSets(string[] keys) return OperateOnSet(SetOperation.Union, keys); } + /// + /// Union sets at keys + /// + public async Task UnionSetsAsync(string[] keys) + { + return await OperateOnSetAsync(SetOperation.Union, keys).ConfigureAwait(false); + } + /// /// Intersect sets at keys /// @@ -613,6 +789,14 @@ public string[] IntersectSets(string[] keys) return OperateOnSet(SetOperation.Intersect, keys); } + /// + /// Intersect sets at keys + /// + public async Task IntersectSetsAsync(string[] keys) + { + return await OperateOnSetAsync(SetOperation.Intersect, keys).ConfigureAwait(false); + } + /// /// Get the diff between the set at index 0 of and all other sets in /// store the result at @@ -625,6 +809,18 @@ public void StoreSetsDifference(string destinationKey, string[] keys) keys); } + /// + /// Get the diff between the set at index 0 of and all other sets in + /// store the result at + /// + public async Task StoreSetsDifferenceAsync(string destinationKey, string[] keys) + { + await OperateOnSetAndStoreAsync( + SetOperation.Difference, + destinationKey, + keys).ConfigureAwait(false); + } + /// /// Union sets at keys /// store the result at @@ -634,6 +830,15 @@ public void UnionSetsAndStore(string destinationKey, string[] keys) OperateOnSetAndStore(SetOperation.Union, destinationKey, keys); } + /// + /// Union sets at keys + /// store the result at + /// + public async Task UnionSetsAndStoreAsync(string destinationKey, string[] keys) + { + await OperateOnSetAndStoreAsync(SetOperation.Union, destinationKey, keys).ConfigureAwait(false); + } + /// /// Intersect sets at keys /// store the result at @@ -643,11 +848,25 @@ public void IntersectSetsAndStore(string destinationKey, string[] keys) OperateOnSetAndStore(SetOperation.Intersect, destinationKey, keys); } + /// + /// Intersect sets at keys + /// store the result at + /// + public async Task IntersectSetsAndStoreAsync(string destinationKey, string[] keys) + { + await OperateOnSetAndStoreAsync(SetOperation.Intersect, destinationKey, keys).ConfigureAwait(false); + } + public bool SetContains(string key, string member) { return Retry(() => this.Database.SetContains(Key(key), member, commandFlags), defaultRetries); } + public async Task SetContainsAsync(string key, string member) + { + return await RetryAsync(() => this.Database.SetContainsAsync(Key(key), member, commandFlags), defaultRetries).ConfigureAwait(false); + } + #region set helpers private string[] OperateOnSet(SetOperation op, string[] keys) { @@ -659,6 +878,16 @@ private string[] OperateOnSet(SetOperation op, string[] keys) return results?.ToStringArray(); } + private async Task OperateOnSetAsync(SetOperation op, string[] keys) + { + if (keys == null || keys.Length == 0) return null; + + var redisKeys = keys.Select(c => (RedisKey)Key(c)).ToArray(); + var results = + await RetryAsync(() => this.Database.SetCombineAsync(op, redisKeys, commandFlags), defaultRetries).ConfigureAwait(false); + + return results?.ToStringArray(); + } private void OperateOnSetAndStore(SetOperation op, string destinationKey, string[] keys) { @@ -669,6 +898,17 @@ private void OperateOnSetAndStore(SetOperation op, string destinationKey, string Retry(() => this.Database.SetCombineAndStore(op, Key(destinationKey), redisKeys, commandFlags), defaultRetries); + } + + private async Task OperateOnSetAndStoreAsync(SetOperation op, string destinationKey, string[] keys) + { + if (keys == null || keys.Length == 0) + return; + + var redisKeys = keys.Select(c => (RedisKey)Key(c)).ToArray(); + + await RetryAsync(() => this.Database.SetCombineAndStoreAsync(op, Key(destinationKey), redisKeys, commandFlags), defaultRetries).ConfigureAwait(false); + } #endregion diff --git a/RedisRepo/RedisRepo.csproj b/RedisRepo/RedisRepo.csproj index d16b1cd..4d55bd1 100644 --- a/RedisRepo/RedisRepo.csproj +++ b/RedisRepo/RedisRepo.csproj @@ -10,8 +10,8 @@ true true - 5.3.0.0 - 5.3.0 + 5.4.0.0 + 5.4.0 5.0.0.0 diff --git a/RedisRepo/RetryUtil.cs b/RedisRepo/RetryUtil.cs index e8968c0..5b99d9b 100644 --- a/RedisRepo/RetryUtil.cs +++ b/RedisRepo/RetryUtil.cs @@ -52,6 +52,42 @@ public static TResult Retry(Func func, int maxAttempts) return result; } + public async static Task RetryAsync(Func> func, int maxAttempts) + { + TResult result = default(TResult); + + for (int attempts = 0; attempts < maxAttempts; attempts++) + { + try + { + result = await func().ConfigureAwait(false); + break; + } + catch (Exception ex) + { + if (!TestExceptionForRetry(ex)) + { + throw; + } + + if (attempts < maxAttempts - 1) + { + LogManager.GetLogger(typeof(RedisContext).FullName).Warn( + ex, $"Retrying, attempt #{attempts}"); + await Task.Delay(RetryDelay * (attempts + 1)).ConfigureAwait(false); + } + else + { + LogManager.GetLogger(typeof(RedisContext).FullName).Error( + ex, $"Failed, attempt #{attempts}"); + throw; + } + } + } + + return result; + } + public static void Retry(Action action, int maxAttempts) { for (int attempts = 0; attempts < maxAttempts; attempts++) @@ -84,6 +120,38 @@ public static void Retry(Action action, int maxAttempts) } } + public async static Task RetryAsync(Func action, int maxAttempts) + { + for (int attempts = 0; attempts < maxAttempts; attempts++) + { + try + { + await action().ConfigureAwait(false); + break; + } + catch (Exception ex) + { + if (!TestExceptionForRetry(ex)) + { + throw; + } + + if (attempts < maxAttempts - 1) + { + LogManager.GetLogger(typeof(RedisContext).FullName).Warn( + ex, $"Retrying, attempt #{attempts}"); + await Task.Delay(RetryDelay * (attempts + 1)).ConfigureAwait(false); + } + else + { + LogManager.GetLogger(typeof(RedisContext).FullName).Error( + ex, $"Failed, attempt #{attempts}"); + throw; + } + } + } + } + public static bool TestExceptionForRetry(Exception ex) { return