From 64a1855b8e76bb8a182d82fafb7c1583dd1822e9 Mon Sep 17 00:00:00 2001 From: Anders Heintz Date: Tue, 28 Oct 2025 14:00:10 +0100 Subject: [PATCH 1/2] Fixed GetOrSet redis read. --- RedisBackedHzCache/RedisBackedHzCache.cs | 18 +++++++++++++++-- RedisBackedHzCache/RedisBackedHzCacheAsync.cs | 20 ++++++++++++++++--- UnitTests/IntegrationTests.cs | 5 +++-- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/RedisBackedHzCache/RedisBackedHzCache.cs b/RedisBackedHzCache/RedisBackedHzCache.cs index dbd7cd2..e75a797 100644 --- a/RedisBackedHzCache/RedisBackedHzCache.cs +++ b/RedisBackedHzCache/RedisBackedHzCache.cs @@ -219,7 +219,20 @@ public void Set(string key, T value, TimeSpan ttl) public T GetOrSet(string key, Func valueFactory, TimeSpan ttl, long maxMsToWaitForFactory = 10000) { - return hzCache.GetOrSet(key, valueFactory, ttl, maxMsToWaitForFactory); + var redisBackedValueFactory = new Func(k => + { + var redisValue = GetRedisValue(k); + if (!redisValue.IsNull) + { + var ttlValue = TTLValue.FromRedisValue(Encoding.ASCII.GetBytes(redisValue.ToString())); + hzCache.SetRaw(k, ttlValue); + return (T)ttlValue.value; + } + + return valueFactory.Invoke(k); + }); + + return hzCache.GetOrSet(key, redisBackedValueFactory, ttl, maxMsToWaitForFactory); } public IList GetOrSetBatch(IList keys, Func, List>> valueFactory) @@ -229,7 +242,8 @@ public IList GetOrSetBatch(IList keys, Func, List GetOrSetBatch(IList keys, Func, List>> valueFactory, TimeSpan ttl) { - using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.GetOrSetBatch, HzActivities.Area.RedisBackedHzCache, key: string.Join(",", keys ?? new List())); + using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.GetOrSetBatch, HzActivities.Area.RedisBackedHzCache, + key: string.Join(",", keys ?? new List())); Func, List>> redisFactory = idList => { // Create a list of redis keys from the list of cache keys diff --git a/RedisBackedHzCache/RedisBackedHzCacheAsync.cs b/RedisBackedHzCache/RedisBackedHzCacheAsync.cs index 880eff6..613621e 100644 --- a/RedisBackedHzCache/RedisBackedHzCacheAsync.cs +++ b/RedisBackedHzCache/RedisBackedHzCacheAsync.cs @@ -57,7 +57,20 @@ public Task SetAsync(string key, T value, TimeSpan ttl) public Task GetOrSetAsync(string key, Func> valueFactory, TimeSpan ttl, long maxMsToWaitForFactory = 10000) { - return hzCache.GetOrSetAsync(key, valueFactory, ttl, maxMsToWaitForFactory); + var redisBackedValueFactory = new Func>(async k => + { + var redisValue = await GetRedisValueAsync(k).ConfigureAwait(false); + if (!redisValue.IsNull) + { + var ttlValue = await TTLValue.FromRedisValueAsync(Encoding.ASCII.GetBytes(redisValue.ToString())).ConfigureAwait(false); + hzCache.SetRaw(k, ttlValue); + return (T)ttlValue.value; + } + + return await valueFactory.Invoke(k).ConfigureAwait(false); + }); + + return hzCache.GetOrSetAsync(key, redisBackedValueFactory, ttl, maxMsToWaitForFactory); } public Task> GetOrSetBatchAsync(IList keys, Func, Task>>> valueFactory) @@ -67,7 +80,8 @@ public Task> GetOrSetBatchAsync(IList keys, Func> GetOrSetBatchAsync(IList keys, Func, Task>>> valueFactory, TimeSpan ttl) { - using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.GetOrSetBatch, HzActivities.Area.RedisBackedHzCache, async: true, key: string.Join(",", keys ?? new List())); + using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.GetOrSetBatch, HzActivities.Area.RedisBackedHzCache, async: true, + key: string.Join(",", keys ?? new List())); Func, Task>>> redisFactory = async idList => { // Create a list of redis keys from the list of cache keys @@ -127,4 +141,4 @@ public Task RemoveAsync(string key) return hzCache.RemoveAsync(key); } } -} +} \ No newline at end of file diff --git a/UnitTests/IntegrationTests.cs b/UnitTests/IntegrationTests.cs index 33d8cb4..c413eaf 100644 --- a/UnitTests/IntegrationTests.cs +++ b/UnitTests/IntegrationTests.cs @@ -298,10 +298,11 @@ public async Task TestRedisGetOrSet() var v1 = c1.GetOrSet("1", _ => new Mocko(10), TimeSpan.FromMinutes(1)); Assert.IsNotNull(v1); Assert.IsTrue(c1.Get("1").num == 10); - await Task.Delay(100); - var c21 = await c2.GetAsync("1"); + await Task.Delay(200); + var c21 = await c2.GetOrSetAsync("1", _ => Task.FromResult(new Mocko(20)), TimeSpan.FromMinutes(1)); Assert.IsTrue(c21.num == 10); Assert.IsTrue(c21.guid != v1.guid); + await c1.RemoveAsync("1"); } From 3506dca6e5180e46966ef4bfc7027209e2cec810 Mon Sep 17 00:00:00 2001 From: Anders Heintz Date: Tue, 28 Oct 2025 14:45:15 +0100 Subject: [PATCH 2/2] Changed implementation to avoid feedbackup/notification loop. Thanks @JoelNygren-Norce! --- RedisBackedHzCache/RedisBackedHzCache.cs | 18 +++++++++------ RedisBackedHzCache/RedisBackedHzCacheAsync.cs | 22 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/RedisBackedHzCache/RedisBackedHzCache.cs b/RedisBackedHzCache/RedisBackedHzCache.cs index e75a797..fa6c6f3 100644 --- a/RedisBackedHzCache/RedisBackedHzCache.cs +++ b/RedisBackedHzCache/RedisBackedHzCache.cs @@ -219,20 +219,24 @@ public void Set(string key, T value, TimeSpan ttl) public T GetOrSet(string key, Func valueFactory, TimeSpan ttl, long maxMsToWaitForFactory = 10000) { - var redisBackedValueFactory = new Func(k => + var value = hzCache.Get(key); + if (value != null) { - var redisValue = GetRedisValue(k); + return value; + } + + if (options.useRedisAs2ndLevelCache) + { + var redisValue = GetRedisValue(key); if (!redisValue.IsNull) { var ttlValue = TTLValue.FromRedisValue(Encoding.ASCII.GetBytes(redisValue.ToString())); - hzCache.SetRaw(k, ttlValue); + hzCache.SetRaw(key, ttlValue); return (T)ttlValue.value; } + } - return valueFactory.Invoke(k); - }); - - return hzCache.GetOrSet(key, redisBackedValueFactory, ttl, maxMsToWaitForFactory); + return hzCache.GetOrSet(key, valueFactory, ttl, maxMsToWaitForFactory); } public IList GetOrSetBatch(IList keys, Func, List>> valueFactory) diff --git a/RedisBackedHzCache/RedisBackedHzCacheAsync.cs b/RedisBackedHzCache/RedisBackedHzCacheAsync.cs index 613621e..84c26ce 100644 --- a/RedisBackedHzCache/RedisBackedHzCacheAsync.cs +++ b/RedisBackedHzCache/RedisBackedHzCacheAsync.cs @@ -55,22 +55,26 @@ public Task SetAsync(string key, T value, TimeSpan ttl) return hzCache.SetAsync(key, value, ttl); } - public Task GetOrSetAsync(string key, Func> valueFactory, TimeSpan ttl, long maxMsToWaitForFactory = 10000) + public async Task GetOrSetAsync(string key, Func> valueFactory, TimeSpan ttl, long maxMsToWaitForFactory = 10000) { - var redisBackedValueFactory = new Func>(async k => + var value = await hzCache.GetAsync(key); + if (value != null) { - var redisValue = await GetRedisValueAsync(k).ConfigureAwait(false); + return value; + } + + if (options.useRedisAs2ndLevelCache) + { + var redisValue = await GetRedisValueAsync(key); if (!redisValue.IsNull) { - var ttlValue = await TTLValue.FromRedisValueAsync(Encoding.ASCII.GetBytes(redisValue.ToString())).ConfigureAwait(false); - hzCache.SetRaw(k, ttlValue); + var ttlValue = await TTLValue.FromRedisValueAsync(Encoding.ASCII.GetBytes(redisValue.ToString())); + hzCache.SetRaw(key, ttlValue); return (T)ttlValue.value; } + } - return await valueFactory.Invoke(k).ConfigureAwait(false); - }); - - return hzCache.GetOrSetAsync(key, redisBackedValueFactory, ttl, maxMsToWaitForFactory); + return await hzCache.GetOrSetAsync(key, valueFactory, ttl, maxMsToWaitForFactory); } public Task> GetOrSetBatchAsync(IList keys, Func, Task>>> valueFactory)