From 428f598919fc5b5fd7b7faea460d164ad07f1542 Mon Sep 17 00:00:00 2001 From: billhong Date: Mon, 11 Sep 2023 18:02:40 +0800 Subject: [PATCH] fix: LockMs/SleepMs only for TiSvc --- build/version.props | 8 +-- .../DefaultCSRedisCachingProvider.Async.cs | 4 ++ .../EasyCachingAbstractProvider.cs | 8 +++ .../IEasyCachingProviderBase.cs | 11 +++ .../DefaultDiskCachingProvider.Async.cs | 6 +- .../DefaultFasterKvCachingProvider.Async.cs | 4 ++ .../HybridCachingProvider.cs | 69 +++++++++---------- .../DefaultInMemoryCachingProvider.Async.cs | 55 +++++++++++++++ .../DefaultLiteDBCachingProvider.Async.cs | 4 ++ .../DefaultMemcachedCachingProvider.Async.cs | 4 ++ .../DefaultRedisCachingProvider.Async.cs | 47 +++++++++++++ .../DefaultSQLiteCachingProvider.Async.cs | 4 ++ .../Diagnostics/MyCachingProvider.cs | 5 ++ .../Fake/FakeDistributedCachingProvider.cs | 5 ++ .../Fake/FakeLocalCachingProvider.cs | 5 ++ 15 files changed, 199 insertions(+), 40 deletions(-) diff --git a/build/version.props b/build/version.props index 5dc47ec6..05918071 100644 --- a/build/version.props +++ b/build/version.props @@ -1,11 +1,11 @@ - 1.9.1 + 1.9.2 1.9.1 - 1.9.1 + 1.9.2 1.9.1 - 1.9.1 - 1.9.1 + 1.9.2 + 1.9.2 1.9.1 1.9.1 1.9.1 diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs index b3c2ad34..0ed0ebdb 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs @@ -10,6 +10,10 @@ public partial class DefaultCSRedisCachingProvider : EasyCachingAbstractProvider { + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } /// /// Existses the async. /// diff --git a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs index b4985128..dffaf6dc 100644 --- a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs +++ b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs @@ -54,6 +54,7 @@ protected EasyCachingAbstractProvider(IDistributedLockFactory lockFactory, BaseP public abstract IDictionary> BaseGetAll(IEnumerable cacheKeys); public abstract Task>> BaseGetAllAsync(IEnumerable cacheKeys, CancellationToken cancellationToken = default); public abstract Task> BaseGetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default); + public abstract Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default); public abstract Task BaseGetAsync(string cacheKey, Type type, CancellationToken cancellationToken = default); public abstract Task> BaseGetAsync(string cacheKey, CancellationToken cancellationToken = default); public abstract IDictionary> BaseGetByPrefix(string prefix); @@ -363,6 +364,13 @@ public async Task>> GetAllAsync(IEnumerable } } + public async Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + if (_lockFactory == null) return await BaseGetAsync(cacheKey, dataRetriever, expirationRetriever, cancellationToken); + // TiSvc 沒用到,不實作 + throw new NotImplementedException(); + } + public async Task> GetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default) { var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(GetAsync), new[] { cacheKey }, expiration)); diff --git a/src/EasyCaching.Core/IEasyCachingProviderBase.cs b/src/EasyCaching.Core/IEasyCachingProviderBase.cs index 3c431f21..a311dfa4 100644 --- a/src/EasyCaching.Core/IEasyCachingProviderBase.cs +++ b/src/EasyCaching.Core/IEasyCachingProviderBase.cs @@ -153,6 +153,17 @@ public interface IEasyCachingProviderBase /// The 1st type parameter. Task> GetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default); + /// + /// Gets the specified cacheKey, dataRetriever and expirationRetriever async. + /// + /// The async. + /// Cache key. + /// Data retriever. + /// Expiration retriever. + /// + /// The 1st type parameter. + Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default); + /// /// Removes cached item by cachekey's prefix. /// diff --git a/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs b/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs index b7908e83..7c1ca908 100644 --- a/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs +++ b/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs @@ -10,7 +10,11 @@ using Microsoft.Extensions.Logging; public partial class DefaultDiskCachingProvider : EasyCachingAbstractProvider - { + { + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } public override async Task BaseExistsAsync(string cacheKey, CancellationToken cancellationToken = default) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); diff --git a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs index c3b04e37..309c188d 100644 --- a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs +++ b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs @@ -9,6 +9,10 @@ namespace EasyCaching.FasterKv { public partial class DefaultFasterKvCachingProvider { + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } public override async Task BaseExistsAsync(string cacheKey, CancellationToken cancellationToken = default) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); diff --git a/src/EasyCaching.HybridCache/HybridCachingProvider.cs b/src/EasyCaching.HybridCache/HybridCachingProvider.cs index 0d63644a..a0614f3d 100644 --- a/src/EasyCaching.HybridCache/HybridCachingProvider.cs +++ b/src/EasyCaching.HybridCache/HybridCachingProvider.cs @@ -304,42 +304,26 @@ public CacheValue Get(string cacheKey) public async Task> GetAsync(string cacheKey, CancellationToken cancellationToken = default) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - - var cacheValue = await _localCache.GetAsync(cacheKey, cancellationToken); - - if (cacheValue.HasValue) - { - return cacheValue; - } - - LogMessage($"local cache can not get the value of {cacheKey}"); - - try - { - cacheValue = await _distributedCache.GetAsync(cacheKey, cancellationToken); - } - catch (Exception ex) - { - LogMessage($"distributed cache get error, [{cacheKey}]", ex); - - if (_options.ThrowIfDistributedCacheError) + var result = await _localCache.GetAsync( + cacheKey, + async () => { - throw; - } - } - - if (cacheValue.HasValue) - { - TimeSpan ts = await GetExpirationAsync(cacheKey, cancellationToken); - - await _localCache.SetAsync(cacheKey, cacheValue.Value, ts, cancellationToken); - - return cacheValue; - } - - LogMessage($"distributed cache can not get the value of {cacheKey}"); - - return CacheValue.NoValue; + var value = default(T); + try + { + value = (await _distributedCache.GetAsync(cacheKey, cancellationToken)).Value; + } + catch (Exception ex) + { + LogMessage($"distributed cache get error, [{cacheKey}]", ex); + if (_options.ThrowIfDistributedCacheError) + throw; + } + return value; + }, + () => GetExpirationAsync(cacheKey, cancellationToken), + cancellationToken); + return result; } /// @@ -780,6 +764,21 @@ public async Task> GetAsync(string cacheKey, Func> data return CacheValue.NoValue; } + /// + /// Gets the specified cacheKey, dataRetriever and expirationRetriever async. + /// + /// The async. + /// Cache key. + /// Data retriever. + /// Expiration retriever. + /// + /// The 1st type parameter. + public async Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + // TiSvc 沒用到,不實作 + throw new NotImplementedException(); + } + /// /// Removes the by prefix. /// diff --git a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs index 96b7050e..a18a12b4 100644 --- a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs +++ b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs @@ -76,6 +76,61 @@ public override async Task> BaseGetAsync(string cacheKey, Func< } } + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + var result = _cache.Get(cacheKey); + if (result.HasValue) + { + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + + CacheStats.OnHit(); + + return result; + } + + CacheStats.OnMiss(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); + + if (!_cache.Add($"{cacheKey}_Lock", 1, TimeSpan.FromMilliseconds(_options.LockMs))) + { + //wait for some ms + await Task.Delay(_options.SleepMs, cancellationToken); + return await GetAsync(cacheKey, dataRetriever, expirationRetriever, cancellationToken); + } + + try + { + var res = await dataRetriever(); + + if (res != null || _options.CacheNulls) + { + TimeSpan expiration = await expirationRetriever(); + await SetAsync(cacheKey, res, expiration, cancellationToken); + //remove mutex key + _cache.Remove($"{cacheKey}_Lock"); + + return new CacheValue(res, true); + } + else + { + //remove mutex key + _cache.Remove($"{cacheKey}_Lock"); + return CacheValue.NoValue; + } + } + catch + { + //remove mutex key + _cache.Remove($"{cacheKey}_Lock"); + throw; + } + } + /// /// Gets the specified cacheKey async. /// diff --git a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs index 466c95fb..97e35905 100644 --- a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs +++ b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs @@ -12,6 +12,10 @@ /// public partial class DefaultLiteDBCachingProvider : EasyCachingAbstractProvider { + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } /// /// Existses the specified cacheKey async. /// diff --git a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs index 1d96f1d5..145028b4 100644 --- a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs +++ b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs @@ -13,6 +13,10 @@ /// public partial class DefaultMemcachedCachingProvider : EasyCachingAbstractProvider { + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } /// /// Gets the specified cacheKey, dataRetriever and expiration async. /// diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs index 3604b5df..edd4d50f 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs @@ -103,6 +103,53 @@ public override async Task> BaseGetAsync(string cacheKey, Func< } } + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + var result = await _cache.StringGetAsync(cacheKey); + if (!result.IsNull) + { + CacheStats.OnHit(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + + var value = _serializer.Deserialize(result); + return new CacheValue(value, true); + } + + CacheStats.OnMiss(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); + + var flag = await _cache.StringSetAsync($"{cacheKey}_Lock", 1, TimeSpan.FromMilliseconds(_options.LockMs), When.NotExists); + + if (!flag) + { + await Task.Delay(_options.SleepMs, cancellationToken); + return await GetAsync(cacheKey, dataRetriever, expirationRetriever, cancellationToken); + } + + var item = await dataRetriever(); + if (item != null || _options.CacheNulls) + { + TimeSpan expiration = await expirationRetriever(); + await SetAsync(cacheKey, item, expiration, cancellationToken); + + //remove mutex key + await _cache.KeyDeleteAsync($"{cacheKey}_Lock"); + return new CacheValue(item, true); + } + else + { + //remove mutex key + await _cache.KeyDeleteAsync($"{cacheKey}_Lock"); + return CacheValue.NoValue; + } + } + /// /// Gets the specified cacheKey async. /// diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs index 5463aec2..a4d09f6b 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs @@ -14,6 +14,10 @@ /// public partial class DefaultSQLiteCachingProvider : EasyCachingAbstractProvider { + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } /// /// Existses the specified cacheKey async. /// diff --git a/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs b/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs index 81257398..3efc185c 100644 --- a/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs @@ -18,6 +18,11 @@ public MyCachingProvider() this.IsDistributedProvider = false; } + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public override bool BaseExists(string cacheKey) { return true; diff --git a/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs b/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs index 67b750dc..820b2412 100644 --- a/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs @@ -205,5 +205,10 @@ public virtual Task TrySetAsync(string cacheKey, T cacheValue, TimeSpan { return Task.FromResult(true); } + + public Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } } diff --git a/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs b/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs index 9638084f..c7cc7606 100644 --- a/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs @@ -205,5 +205,10 @@ public Task TrySetAsync(string cacheKey, T cacheValue, TimeSpan expirat { return Task.FromResult(true); } + + public Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } }