Skip to content

Commit c2e4f69

Browse files
authored
feat(caching): HashIncrementAsync, HashDecrementAsync support sliding expiration (#314)
* fix(Caching): Fix HashIncrementAsync, HashDecrementAsync do not support sliding expiration * chore(Caching): Dealing with bad code smells * chore(Caching): Add description
1 parent 56e49f8 commit c2e4f69

File tree

4 files changed

+92
-34
lines changed

4 files changed

+92
-34
lines changed

src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/DistributedCacheClientBase.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ public override Task RemoveAsync<T>(string key, Action<CacheOptions>? action = n
5555
public abstract Task<long> HashIncrementAsync(
5656
string key,
5757
long value = 1,
58-
Action<CacheOptions>? action = null);
58+
Action<CacheOptions>? action = null,
59+
CacheEntryOptions? options = null);
5960

60-
public abstract Task<long> HashDecrementAsync(string key,
61+
public abstract Task<long?> HashDecrementAsync(string key,
6162
long value = 1L,
6263
long defaultMinVal = 0L,
63-
Action<CacheOptions>? action = null);
64+
Action<CacheOptions>? action = null,
65+
CacheEntryOptions? options = null);
6466

6567
public virtual bool KeyExpire(string key, TimeSpan? absoluteExpirationRelativeToNow)
6668
=> KeyExpire(key, new CacheEntryOptions(absoluteExpirationRelativeToNow));

src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/IDistributedCacheClient.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,27 @@ public interface IDistributedCacheClient : ICacheClient
119119
/// <param name="key">cache key</param>
120120
/// <param name="value">incremental increment, must be greater than 0</param>
121121
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
122-
/// <returns></returns>
123-
Task<long> HashIncrementAsync(string key, long value = 1L, Action<CacheOptions>? action = null);
122+
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
123+
/// <returns>Returns the field value after the increment operation</returns>
124+
Task<long> HashIncrementAsync(string key,
125+
long value = 1L,
126+
Action<CacheOptions>? action = null,
127+
CacheEntryOptions? options = null);
124128

125129
/// <summary>
126130
/// Descending Hash
127131
/// </summary>
128132
/// <param name="key">cache key</param>
129133
/// <param name="value">decrement increment, must be greater than 0</param>
130-
/// <param name="defaultMinVal">critical value, must be greater than or equal to 0</param>
134+
/// <param name="defaultMinVal">critical value</param>
131135
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
132-
/// <returns></returns>
133-
Task<long> HashDecrementAsync(string key, long value = 1L, long defaultMinVal = 0L, Action<CacheOptions>? action = null);
136+
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
137+
/// <returns>Returns null on failure, and returns the field value after the decrement operation on success</returns>
138+
Task<long?> HashDecrementAsync(string key,
139+
long value = 1L,
140+
long defaultMinVal = 0L,
141+
Action<CacheOptions>? action = null,
142+
CacheEntryOptions? options = null);
134143

135144
/// <summary>
136145
/// Set cache lifetime

src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClient.cs

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -386,39 +386,97 @@ public override Task SubscribeAsync<T>(string channel, Action<SubscribeOptions<T
386386

387387
#region Hash
388388

389-
public override Task<long> HashIncrementAsync(string key, long value = 1, Action<CacheOptions>? action = null)
389+
/// <summary>
390+
/// Increment Hash
391+
/// </summary>
392+
/// <param name="key">cache key</param>
393+
/// <param name="value">incremental increment, must be greater than 0</param>
394+
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
395+
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
396+
/// <returns>Returns the field value after the increment operation</returns>
397+
public override async Task<long> HashIncrementAsync(
398+
string key,
399+
long value = 1,
400+
Action<CacheOptions>? action = null,
401+
CacheEntryOptions? options = null)
390402
{
391-
if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0");
403+
CheckParametersByHashIncrementOrHashDecrement(value);
404+
405+
var script = $@"
406+
local exist = redis.call('EXISTS', KEYS[1])
407+
if(exist ~= 1) then
408+
redis.call('HMSET', KEYS[1], KEYS[3], ARGV[1], KEYS[4], ARGV[2])
409+
if ARGV[3] ~= '-1' then
410+
redis.call('EXPIRE', KEYS[1], ARGV[3])
411+
end
412+
end
413+
return redis.call('HINCRBY', KEYS[1], KEYS[2], {value})";
414+
415+
var formattedKey = FormatCacheKey<long>(key, action);
416+
var result = (long)await Db.ScriptEvaluateAsync(script,
417+
new RedisKey[]
418+
{ formattedKey, Const.DATA_KEY, Const.ABSOLUTE_EXPIRATION_KEY, Const.SLIDING_EXPIRATION_KEY },
419+
GetRedisValues(options));
420+
421+
await RefreshAsync(formattedKey);
392422

393-
return Db.HashIncrementAsync(FormatCacheKey<long>(key, action), Const.DATA_KEY, value);
423+
return result;
424+
}
425+
426+
private static void CheckParametersByHashIncrementOrHashDecrement(long value = 1)
427+
{
428+
if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0");
394429
}
395430

396-
public override async Task<long> HashDecrementAsync(
431+
/// <summary>
432+
/// Descending Hash
433+
/// </summary>
434+
/// <param name="key">cache key</param>
435+
/// <param name="value">decrement increment, must be greater than 0</param>
436+
/// <param name="defaultMinVal">critical value</param>
437+
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
438+
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
439+
/// <returns>Returns null on failure, and returns the field value after the decrement operation on success</returns>
440+
public override async Task<long?> HashDecrementAsync(
397441
string key,
398442
long value = 1L,
399443
long defaultMinVal = 0L,
400-
Action<CacheOptions>? action = null)
444+
Action<CacheOptions>? action = null,
445+
CacheEntryOptions? options = null)
401446
{
402-
CheckParametersByHashDecrement(value, defaultMinVal);
447+
CheckParametersByHashIncrementOrHashDecrement(value);
403448

404449
var script = $@"
450+
local exist = redis.call('EXISTS', KEYS[1])
451+
if(exist ~= 1) then
452+
redis.call('HMSET', KEYS[1], KEYS[3], ARGV[1], KEYS[4], ARGV[2])
453+
if ARGV[3] ~= '-1' then
454+
redis.call('EXPIRE', KEYS[1], ARGV[3])
455+
end
456+
end
457+
405458
local result = redis.call('HGET', KEYS[1], KEYS[2])
459+
if result then
460+
else
461+
result = 0
462+
end
406463
if tonumber(result) > {defaultMinVal} then
407464
result = redis.call('HINCRBY', KEYS[1], KEYS[2], {0 - value})
408465
return result
409466
else
410-
return -1
467+
return nil
411468
end";
412-
var result = (long)await Db.ScriptEvaluateAsync(script, new RedisKey[] { FormatCacheKey<long>(key, action), Const.DATA_KEY });
413-
414-
return result;
415-
}
469+
var formattedKey = FormatCacheKey<long>(key, action);
470+
var result = await Db.ScriptEvaluateAsync(
471+
script,
472+
new RedisKey[] { formattedKey, Const.DATA_KEY , Const.ABSOLUTE_EXPIRATION_KEY, Const.SLIDING_EXPIRATION_KEY },
473+
GetRedisValues(options));
474+
await RefreshAsync(formattedKey);
416475

417-
private static void CheckParametersByHashDecrement(long value = 1, long defaultMinVal = 0L)
418-
{
419-
if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0");
476+
if (result.IsNull)
477+
return null;
420478

421-
if (defaultMinVal < 0) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than or equal to 0");
479+
return (long)result;
422480
}
423481

424482
#endregion

src/Contrib/Caching/Distributed/Tests/Masa.Contrib.Caching.Distributed.StackExchangeRedis.Tests/DistributedCacheClientTest.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -531,17 +531,6 @@ await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async ()
531531
=> await _distributedCacheClient.HashDecrementAsync(key, value));
532532
}
533533

534-
[DataTestMethod]
535-
[DataRow("hash_test", -1)]
536-
[DataRow("hash_test", -2)]
537-
public async Task TestHashDecrementAndMinalValueLessThan0Async(string key, long minVal)
538-
{
539-
await _distributedCacheClient.RemoveAsync(key);
540-
541-
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async ()
542-
=> await _distributedCacheClient.HashDecrementAsync(key, 1, minVal));
543-
}
544-
545534
[DataTestMethod]
546535
[DataRow("test_expire")]
547536
public void TestKeyExpireAndSpecialTimeSpan(string key)

0 commit comments

Comments
 (0)