From d03cca6e29b34030ff58190df9d9175c7ff174b3 Mon Sep 17 00:00:00 2001 From: Jude Vajira Guanasekera Date: Fri, 4 Nov 2022 06:07:49 +0530 Subject: [PATCH 01/12] added a HybridCachingOption to throw an error if the distributed cache throws an exception (#413) --- .../Configurations/HybridCachingOptions.cs | 6 + .../HybridCachingProvider.cs | 141 +++++++++++++++--- .../CachingTests/HybridCachingTest.cs | 15 +- 3 files changed, 135 insertions(+), 27 deletions(-) diff --git a/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs b/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs index a2e521e0..0840f493 100644 --- a/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs +++ b/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs @@ -15,6 +15,12 @@ public class HybridCachingOptions /// true if enable logging; otherwise, false. public bool EnableLogging { get; set; } + /// + /// Gets or sets a value indicating whether an exception should be thrown if an error on the distributed cache has occurred + /// + /// true if distributed cache exceptions should not be ignored; otherwise, false. + public bool ThrowIfDistributedCacheError { get; set; } + /// /// local cache provider name /// diff --git a/src/EasyCaching.HybridCache/HybridCachingProvider.cs b/src/EasyCaching.HybridCache/HybridCachingProvider.cs index cc6f830c..e8594fa8 100644 --- a/src/EasyCaching.HybridCache/HybridCachingProvider.cs +++ b/src/EasyCaching.HybridCache/HybridCachingProvider.cs @@ -1,10 +1,5 @@ namespace EasyCaching.HybridCache { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; using EasyCaching.Core; using EasyCaching.Core.Bus; using Microsoft.Extensions.Logging; @@ -12,6 +7,11 @@ using Polly.Fallback; using Polly.Retry; using Polly.Wrap; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; /// /// Hybrid caching provider. @@ -123,7 +123,7 @@ string name /// Message. private void OnMessage(EasyCachingMessage message) { - // each clients will recive the message, current client should ignore. + // each clients will receive the message, current client should ignore. if (!string.IsNullOrWhiteSpace(message.Id) && message.Id.Equals(_cacheId, StringComparison.OrdinalIgnoreCase)) return; @@ -136,7 +136,7 @@ private void OnMessage(EasyCachingMessage message) LogMessage($"remove local cache that pattern is {pattern}"); - return; + return; } // remove by prefix @@ -178,6 +178,11 @@ public bool Exists(string cacheKey) catch (Exception ex) { LogMessage($"Check cache key exists error [{cacheKey}] ", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } flag = _localCache.Exists(cacheKey); @@ -186,7 +191,7 @@ public bool Exists(string cacheKey) } /// - /// Existses the specified cacheKey async. + /// Exists the specified cacheKey async. /// /// The async. /// Cache key. @@ -206,6 +211,11 @@ public async Task ExistsAsync(string cacheKey, CancellationToken cancellat catch (Exception ex) { LogMessage($"Check cache key [{cacheKey}] exists error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } flag = await _localCache.ExistsAsync(cacheKey, cancellationToken); @@ -239,12 +249,17 @@ public CacheValue Get(string cacheKey) catch (Exception ex) { LogMessage($"distributed cache get error, [{cacheKey}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (cacheValue.HasValue) { TimeSpan ts = GetExpiration(cacheKey); - + _localCache.Set(cacheKey, cacheValue.Value, ts); return cacheValue; @@ -282,6 +297,11 @@ public async Task> GetAsync(string cacheKey, CancellationToken catch (Exception ex) { LogMessage($"distributed cache get error, [{cacheKey}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (cacheValue.HasValue) @@ -314,6 +334,11 @@ public void Remove(string cacheKey) catch (Exception ex) { LogMessage($"remove cache key [{cacheKey}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } _localCache.Remove(cacheKey); @@ -340,6 +365,11 @@ public async Task RemoveAsync(string cacheKey, CancellationToken cancellationTok catch (Exception ex) { LogMessage($"remove cache key [{cacheKey}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } await _localCache.RemoveAsync(cacheKey, cancellationToken); @@ -368,6 +398,11 @@ public void Set(string cacheKey, T cacheValue, TimeSpan expiration) catch (Exception ex) { LogMessage($"set cache key [{cacheKey}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } // When create/update cache, send message to bus so that other clients can remove it. @@ -396,6 +431,11 @@ public async Task SetAsync(string cacheKey, T cacheValue, TimeSpan expiration catch (Exception ex) { LogMessage($"set cache key [{cacheKey}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } // When create/update cache, send message to bus so that other clients can remove it. @@ -425,6 +465,11 @@ public bool TrySet(string cacheKey, T cacheValue, TimeSpan expiration) { distributedError = true; LogMessage($"tryset cache key [{cacheKey}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (flag && !distributedError) @@ -473,6 +518,11 @@ public async Task TrySetAsync(string cacheKey, T cacheValue, TimeSpan e { distributedError = true; LogMessage($"tryset cache key [{cacheKey}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (flag && !distributedError) @@ -514,6 +564,11 @@ public void SetAll(IDictionary value, TimeSpan expiration) catch (Exception ex) { LogMessage($"set all from distributed provider error [{string.Join(",", value.Keys)}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } // send message to bus @@ -539,6 +594,11 @@ public async Task SetAllAsync(IDictionary value, TimeSpan expirati catch (Exception ex) { LogMessage($"set all from distributed provider error [{string.Join(",", value.Keys)}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } // send message to bus @@ -555,11 +615,16 @@ public void RemoveAll(IEnumerable cacheKeys) try { - _distributedCache.RemoveAllAsync(cacheKeys); + _distributedCache.RemoveAll(cacheKeys); } catch (Exception ex) { LogMessage($"remove all from distributed provider error [{string.Join(",", cacheKeys)}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } _localCache.RemoveAll(cacheKeys); @@ -585,6 +650,11 @@ public async Task RemoveAllAsync(IEnumerable cacheKeys, CancellationToke catch (Exception ex) { LogMessage($"remove all async from distributed provider error [{string.Join(",", cacheKeys)}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } await _localCache.RemoveAllAsync(cacheKeys, cancellationToken); @@ -620,6 +690,11 @@ public CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan exp catch (Exception ex) { LogMessage($"get with data retriever from distributed provider error [{cacheKey}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (result.HasValue) @@ -662,13 +737,18 @@ public async Task> GetAsync(string cacheKey, Func> data catch (Exception ex) { LogMessage($"get async with data retriever from distributed provider error [{cacheKey}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (result.HasValue) { TimeSpan ts = await GetExpirationAsync(cacheKey, cancellationToken); - _localCache.Set(cacheKey, result.Value, ts); + await _localCache.SetAsync(cacheKey, result.Value, ts, cancellationToken); return result; } @@ -691,6 +771,11 @@ public void RemoveByPrefix(string prefix) catch (Exception ex) { LogMessage($"remove by prefix [{prefix}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } _localCache.RemoveByPrefix(prefix); @@ -716,14 +801,19 @@ public async Task RemoveByPrefixAsync(string prefix, CancellationToken cancellat catch (Exception ex) { LogMessage($"remove by prefix [{prefix}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } - await _localCache.RemoveByPrefixAsync(prefix); + await _localCache.RemoveByPrefixAsync(prefix, cancellationToken); // send message to bus in order to notify other clients. await _busAsyncWrap.ExecuteAsync(async (ct) => await _bus.PublishAsync(_options.TopicName, new EasyCachingMessage { Id = _cacheId, CacheKeys = new string[] { prefix }, IsPrefix = true }, ct), cancellationToken); } - + /// /// Removes the by pattern async. /// @@ -741,14 +831,19 @@ public async Task RemoveByPatternAsync(string pattern, CancellationToken cancell catch (Exception ex) { LogMessage($"remove by pattern [{pattern}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } - await _localCache.RemoveByPatternAsync(pattern); + await _localCache.RemoveByPatternAsync(pattern, cancellationToken); // send message to bus in order to notify other clients. - await _busAsyncWrap.ExecuteAsync(async (ct) => await _bus.PublishAsync(_options.TopicName, new EasyCachingMessage { Id = _cacheId, CacheKeys = new string[] { pattern }, IsPattern = true}, ct), cancellationToken); + await _busAsyncWrap.ExecuteAsync(async (ct) => await _bus.PublishAsync(_options.TopicName, new EasyCachingMessage { Id = _cacheId, CacheKeys = new string[] { pattern }, IsPattern = true }, ct), cancellationToken); } - + /// /// Removes the by pattern. /// @@ -765,12 +860,17 @@ public void RemoveByPattern(string pattern) catch (Exception ex) { LogMessage($"remove by pattern [{pattern}] error", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } _localCache.RemoveByPattern(pattern); // send message to bus - _busSyncWrap.Execute(() => _bus.Publish(_options.TopicName, new EasyCachingMessage { Id = _cacheId, CacheKeys = new string[] { pattern }, IsPattern = true})); + _busSyncWrap.Execute(() => _bus.Publish(_options.TopicName, new EasyCachingMessage { Id = _cacheId, CacheKeys = new string[] { pattern }, IsPattern = true })); } /// @@ -855,12 +955,17 @@ public async Task GetAsync(string cacheKey, Type type, CancellationToken catch (Exception ex) { LogMessage($"distributed cache get error, [{cacheKey}]", ex); + + if (_options.ThrowIfDistributedCacheError) + { + throw; + } } if (cacheValue != null) { TimeSpan ts = await GetExpirationAsync(cacheKey, cancellationToken); - + await _localCache.SetAsync(cacheKey, cacheValue, ts, cancellationToken); return cacheValue; diff --git a/test/EasyCaching.UnitTests/CachingTests/HybridCachingTest.cs b/test/EasyCaching.UnitTests/CachingTests/HybridCachingTest.cs index b4170b95..8f7acd68 100644 --- a/test/EasyCaching.UnitTests/CachingTests/HybridCachingTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/HybridCachingTest.cs @@ -1,24 +1,20 @@ namespace EasyCaching.UnitTests { - using EasyCaching.Bus.Redis; using EasyCaching.Core; using EasyCaching.Core.Bus; using EasyCaching.HybridCache; - using EasyCaching.InMemory; - using EasyCaching.Redis; + using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; + using System.Threading; using System.Threading.Tasks; using Xunit; - using FakeItEasy; - using System.Threading; public class HybridCachingTest //: BaseCachingProviderTest { - private string _namespace; + private readonly string _namespace; private IHybridCachingProvider hybridCaching_1; - private IEasyCachingProviderFactory factory; private HybridCachingProvider fakeHybrid; private IEasyCachingProviderFactory fakeFactory; @@ -32,6 +28,7 @@ public HybridCachingTest() var options = new HybridCachingOptions { EnableLogging = false, + ThrowIfDistributedCacheError = false, TopicName = "test_topic", LocalCacheProviderName = "m1", DistributedCacheProviderName = "myredis", @@ -54,6 +51,7 @@ public HybridCachingTest() option.UseHybrid(config => { config.EnableLogging = false; + config.ThrowIfDistributedCacheError = false; config.TopicName = "test_topic"; config.LocalCacheProviderName = "m1"; config.DistributedCacheProviderName = "myredis"; @@ -68,7 +66,6 @@ public HybridCachingTest() }); IServiceProvider serviceProvider = services.BuildServiceProvider(); - factory = serviceProvider.GetService(); var bus = serviceProvider.GetService(); @@ -171,7 +168,7 @@ public async Task Send_Msg_Async_Throw_Exception_Should_Not_Break() Assert.True(true); } - + [Fact] public void Distributed_Remove_Throw_Exception_Should_Not_Break() { From a24516634ebe8766b2bee32e4dc65f2b9def72a7 Mon Sep 17 00:00:00 2001 From: Catcher Wong Date: Sun, 6 Nov 2022 22:31:42 +0800 Subject: [PATCH 02/12] feat(memory): support evicted event hook(#294) (#414) --- .../Internal/IInMemoryCaching.cs | 12 ++++++ .../Internal/InMemoryCaching.cs | 33 +++++++++++------ .../CachingTests/MemoryCachingProviderTest.cs | 37 +++++++++++++++++++ 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs b/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs index 3060b570..944b6dbd 100644 --- a/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs +++ b/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs @@ -24,5 +24,17 @@ public interface IInMemoryCaching bool Replace(string key, T value, TimeSpan? expiresIn = null); void Clear(string prefix = ""); TimeSpan GetExpiration(string key); + + event EventHandler Evicted; + } + + public class EvictedEventArgs : EventArgs + { + public EvictedEventArgs(string key ) + { + this.Key = key; + } + + public string Key { get; private set; } } } diff --git a/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs b/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs index 4a9ea1a7..c93e6366 100644 --- a/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs +++ b/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs @@ -17,6 +17,8 @@ public class InMemoryCaching : IInMemoryCaching private long _cacheSize = 0L; private const string _UPTOLIMIT_KEY = "inter_up_to_limit_key"; + public event EventHandler Evicted; + public InMemoryCaching(string name, InMemoryCachingOptions optionsAccessor) { ArgumentCheck.NotNull(optionsAccessor, nameof(optionsAccessor)); @@ -32,7 +34,7 @@ public InMemoryCaching(string name, InMemoryCachingOptions optionsAccessor) public void Clear(string prefix = "") { if (string.IsNullOrWhiteSpace(prefix)) - { + { _memory.Clear(); if (_options.SizeLimit.HasValue) @@ -53,9 +55,13 @@ public int GetCount(string prefix = "") internal void RemoveExpiredKey(string key) { - bool flag = _memory.TryRemove(key, out _); - if (_options.SizeLimit.HasValue && flag) - Interlocked.Decrement(ref _cacheSize); + if (_memory.TryRemove(key, out _)) + { + Evicted?.Invoke(this, new EvictedEventArgs(key)); + + if (_options.SizeLimit.HasValue) + Interlocked.Decrement(ref _cacheSize); + } } public CacheValue Get(string key) @@ -189,7 +195,7 @@ private bool SetInternal(CacheEntry entry, bool addOnly = false) _memory.AddOrUpdate(deep.Key, deep, (k, cacheEntry) => deep); - if(_options.SizeLimit.HasValue) + if (_options.SizeLimit.HasValue) Interlocked.Increment(ref _cacheSize); } } @@ -239,7 +245,7 @@ public int RemoveAll(IEnumerable keys = null) continue; if (_memory.TryRemove(key, out _)) - { + { removed++; if (_options.SizeLimit.HasValue) Interlocked.Decrement(ref _cacheSize); @@ -254,7 +260,7 @@ public bool Remove(string key) bool flag = _memory.TryRemove(key, out _); if (_options.SizeLimit.HasValue && !key.Equals(_UPTOLIMIT_KEY) && flag) - { + { Interlocked.Decrement(ref _cacheSize); } @@ -270,10 +276,10 @@ public int RemoveByPrefix(string prefix) public int RemoveByPattern(string searchKey, SearchKeyPattern searchPattern) { var keysToRemove = _memory.Keys.Where(x => FilterByPattern(x, searchKey, searchPattern)).ToList(); - + return RemoveAll(keysToRemove); } - + private static bool FilterByPattern(string key, string searchKey, SearchKeyPattern searchKeyPattern) { switch (searchKeyPattern) @@ -336,7 +342,10 @@ private void ScanForExpiredItems(InMemoryCaching cache) var now = SystemClock.UtcNow; foreach (var entry in cache._memory.Values.Where(x => x.ExpiresAt < now)) { - cache.Remove(entry.Key); + if (cache.Remove(entry.Key)) + { + Evicted?.Invoke(this, new EvictedEventArgs(entry.Key)); + } } } @@ -403,7 +412,7 @@ internal object Value public T GetValue(bool isDeepClone = true) { object val = Value; - + var t = typeof(T); if (t == TypeHelper.BoolType || t == TypeHelper.StringType || t == TypeHelper.CharType || t == TypeHelper.DateTimeType || t.IsNumeric()) @@ -412,7 +421,7 @@ public T GetValue(bool isDeepClone = true) if (t == TypeHelper.NullableBoolType || t == TypeHelper.NullableCharType || t == TypeHelper.NullableDateTimeType || t.IsNullableNumeric()) return val == null ? default(T) : (T)Convert.ChangeType(val, Nullable.GetUnderlyingType(t)); - return isDeepClone + return isDeepClone ? DeepClonerGenerator.CloneObject((T)val) : (T)val; } diff --git a/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs index 41f54a01..899b2589 100644 --- a/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs @@ -118,6 +118,43 @@ public void GetDatabase_Should_Succeed() Assert.NotNull(db); Assert.IsType(db); } + + [Fact] + public void Evicted_Event_Should_Trigger_When_StartScanForExpiredItems() + { + var key1 = "Evicted-111"; + var key2 = "Evicted-112"; + + var db = (IInMemoryCaching)_provider.Database; + + db.Evicted += (s,e) => + { + Assert.Equal(key1, e.Key); + }; + + _provider.Set(key1, "111", TimeSpan.FromMilliseconds(500)); + _provider.Set(key2, "112", TimeSpan.FromMilliseconds(6000)); + + System.Threading.Thread.Sleep(1000); + } + + [Fact] + public void Evicted_Event_Should_Trigger_When_GetExpiredItems() + { + var key1 = "Evicted-211"; + + var db = (IInMemoryCaching)_provider.Database; + + db.Evicted += (s, e) => + { + Assert.Equal(key1, e.Key); + }; + + _provider.Set(key1, "211", TimeSpan.FromMilliseconds(500)); + System.Threading.Thread.Sleep(1000); + _provider.Get(key1); + System.Threading.Thread.Sleep(500); + } } public class MemoryCachingProviderWithFactoryTest : BaseCachingProviderWithFactoryTest From 83b8ec88f33aba512be6e2f832a6e70a3c2a8ebe Mon Sep 17 00:00:00 2001 From: InCerryGit <36690780+InCerryGit@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:54:47 +0800 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20typo=20=E2=80=98BaseGetDatabse?= =?UTF-8?q?=E2=80=99=20(#417)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: typo ‘BaseGetDatabse’ * fix: miss file --- src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs | 2 +- src/EasyCaching.Core/EasyCachingAbstractProvider.cs | 4 ++-- src/EasyCaching.Disk/DefaultDiskCachingProvider.cs | 2 +- src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs | 2 +- src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs | 2 +- src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs | 2 +- src/EasyCaching.Redis/DefaultRedisCachingProvider.cs | 2 +- src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs | 2 +- test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs index f335eb7e..8b36e3e5 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs @@ -532,6 +532,6 @@ public override TimeSpan BaseGetExpiration(string cacheKey) /// public override ProviderInfo BaseGetProviderInfo() => _info; - public override object BaseGetDatabse() => _cache; + public override object BaseGetDatabase() => _cache; } } diff --git a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs index e7e348af..67ff62f7 100644 --- a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs +++ b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs @@ -32,7 +32,7 @@ public abstract class EasyCachingAbstractProvider : IEasyCachingProvider public CachingProviderType CachingProviderType => this.ProviderType; public CacheStats CacheStats => this.ProviderStats; - public object Database => BaseGetDatabse(); + public object Database => BaseGetDatabase(); protected EasyCachingAbstractProvider() { } @@ -42,7 +42,7 @@ protected EasyCachingAbstractProvider(IDistributedLockFactory lockFactory, BaseP _options = options; } - public abstract object BaseGetDatabse(); + public abstract object BaseGetDatabase(); public abstract bool BaseExists(string cacheKey); public abstract Task BaseExistsAsync(string cacheKey, CancellationToken cancellationToken = default); public abstract void BaseFlush(); diff --git a/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs b/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs index d3541701..ff6ed533 100644 --- a/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs +++ b/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs @@ -712,6 +712,6 @@ private void DeleteDirectory(string path) public override ProviderInfo BaseGetProviderInfo() => _info; - public override object BaseGetDatabse() => throw new Exception("Disk provider don't support this "); + public override object BaseGetDatabase() => throw new Exception("Disk provider don't support this "); } } diff --git a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs index 7258cfc6..e1faa2fc 100644 --- a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs +++ b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs @@ -396,6 +396,6 @@ public override TimeSpan BaseGetExpiration(string cacheKey) /// public override ProviderInfo BaseGetProviderInfo() => _info; - public override object BaseGetDatabse() => _cache; + public override object BaseGetDatabase() => _cache; } } diff --git a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs index 9531e069..7514987f 100644 --- a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs +++ b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs @@ -443,6 +443,6 @@ public override TimeSpan BaseGetExpiration(string cacheKey) /// public override ProviderInfo BaseGetProviderInfo() => _info; - public override object BaseGetDatabse() => _cache; + public override object BaseGetDatabase() => _cache; } } diff --git a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs index e380ab3c..107bef3f 100644 --- a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs +++ b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs @@ -430,7 +430,7 @@ public override ProviderInfo BaseGetProviderInfo() return _info; } - public override object BaseGetDatabse() => _memcachedClient; + public override object BaseGetDatabase() => _memcachedClient; private void OnCacheHit(string cacheKey) { diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs index f3836fb6..c6e4f61d 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs @@ -560,6 +560,6 @@ public override TimeSpan BaseGetExpiration(string cacheKey) /// public override ProviderInfo BaseGetProviderInfo() => _info; - public override object BaseGetDatabse() => _cache; + public override object BaseGetDatabase() => _cache; } } diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs index 47c178d1..e9e6aafa 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs @@ -429,6 +429,6 @@ public override TimeSpan BaseGetExpiration(string cacheKey) /// public override ProviderInfo BaseGetProviderInfo() => _info; - public override object BaseGetDatabse() => _cache; + public override object BaseGetDatabase() => _cache; } } diff --git a/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs b/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs index 84556c6c..a64ebad7 100644 --- a/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs @@ -176,7 +176,7 @@ public override Task BaseTrySetAsync(string cacheKey, T cacheValue, Tim return Task.FromResult(false); } - public override object BaseGetDatabse() + public override object BaseGetDatabase() { return null; } From 5b093dc309989dbafb92d050be65ccddd3876c6a Mon Sep 17 00:00:00 2001 From: InCerryGit <36690780+InCerryGit@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:11:02 +0800 Subject: [PATCH 04/12] feat(FasterKv): Add FasterKv support (#418) * feat(FasterKv): add FasterKv caching provider support. * fix wrong summary --- EasyCaching.sln | 7 + .../Controllers/ValuesController.cs | 2 +- .../EasyCaching.Demo.Providers.csproj | 1 + sample/EasyCaching.Demo.Providers/Startup.cs | 10 +- .../appsettings.json | 4 + .../Internal/CachingProviderType.cs | 1 + .../Internal/EasyCachingConstValue.cs | 36 +- .../ClientSessionPooledObjectPolicy.cs | 27 ++ .../Configurations/FasterKvCachingOptions.cs | 74 ++++ .../FasterKvCachingOptionsExtensions.cs | 68 ++++ .../FasterKvOptionsExtension.cs | 61 +++ .../DefaultFasterKvCachingProvider.Async.cs | 223 +++++++++++ .../DefaultFasterKvCachingProvider.cs | 377 ++++++++++++++++++ .../EasyCaching.FasterKv.csproj | 18 + src/EasyCaching.FasterKv/StoreFunctions.cs | 34 ++ .../EasyCaching.PerformanceTests.csproj | 6 +- .../FasterKvBenchmark.cs | 141 +++++++ test/EasyCaching.PerformanceTests/Program.cs | 3 +- .../CachingTests/BaseCachingProviderTest.cs | 8 +- .../FasterKvCachingProviderTest.cs | 164 ++++++++ .../EasyCaching.UnitTests.csproj | 3 +- 21 files changed, 1245 insertions(+), 23 deletions(-) create mode 100644 src/EasyCaching.FasterKv/ClientSessionPooledObjectPolicy.cs create mode 100644 src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptions.cs create mode 100644 src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptionsExtensions.cs create mode 100644 src/EasyCaching.FasterKv/Configurations/FasterKvOptionsExtension.cs create mode 100644 src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs create mode 100644 src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs create mode 100644 src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj create mode 100644 src/EasyCaching.FasterKv/StoreFunctions.cs create mode 100644 test/EasyCaching.PerformanceTests/FasterKvBenchmark.cs create mode 100644 test/EasyCaching.UnitTests/CachingTests/FasterKvCachingProviderTest.cs diff --git a/EasyCaching.sln b/EasyCaching.sln index ff571815..3f2504a3 100644 --- a/EasyCaching.sln +++ b/EasyCaching.sln @@ -74,6 +74,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Bus.ConfluentKa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Bus.Zookeeper", "bus\EasyCaching.Bus.Zookeeper\EasyCaching.Bus.Zookeeper.csproj", "{5E488583-391E-4E15-83C1-7301B4FE79AE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.FasterKv", "src\EasyCaching.FasterKv\EasyCaching.FasterKv.csproj", "{7191E567-38DF-4879-82E1-73EC618AFCAC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -196,6 +198,10 @@ Global {5E488583-391E-4E15-83C1-7301B4FE79AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E488583-391E-4E15-83C1-7301B4FE79AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E488583-391E-4E15-83C1-7301B4FE79AE}.Release|Any CPU.Build.0 = Release|Any CPU + {7191E567-38DF-4879-82E1-73EC618AFCAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7191E567-38DF-4879-82E1-73EC618AFCAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7191E567-38DF-4879-82E1-73EC618AFCAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7191E567-38DF-4879-82E1-73EC618AFCAC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -230,6 +236,7 @@ Global {4FCF16BF-5E21-4B74-AB45-3C121ADF1485} = {15070C49-A507-4844-BCFE-D319CFBC9A63} {F7FBADEB-D766-4595-949A-07104B52692C} = {B337509B-75F9-4851-821F-9BBE87C4E4BC} {5E488583-391E-4E15-83C1-7301B4FE79AE} = {B337509B-75F9-4851-821F-9BBE87C4E4BC} + {7191E567-38DF-4879-82E1-73EC618AFCAC} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63A57886-054B-476C-AAE1-8D7C8917682E} diff --git a/sample/EasyCaching.Demo.Providers/Controllers/ValuesController.cs b/sample/EasyCaching.Demo.Providers/Controllers/ValuesController.cs index d08da6a7..d7f8878a 100644 --- a/sample/EasyCaching.Demo.Providers/Controllers/ValuesController.cs +++ b/sample/EasyCaching.Demo.Providers/Controllers/ValuesController.cs @@ -8,7 +8,7 @@ [Route("api/[controller]")] public class ValuesController : Controller { - //1. InMemory,Memcached,Redis,SQLite + //1. InMemory,Memcached,Redis,SQLite,FasterKv private readonly IEasyCachingProvider _provider; public ValuesController(IEasyCachingProvider provider) diff --git a/sample/EasyCaching.Demo.Providers/EasyCaching.Demo.Providers.csproj b/sample/EasyCaching.Demo.Providers/EasyCaching.Demo.Providers.csproj index 0fb6da6f..116c4206 100644 --- a/sample/EasyCaching.Demo.Providers/EasyCaching.Demo.Providers.csproj +++ b/sample/EasyCaching.Demo.Providers/EasyCaching.Demo.Providers.csproj @@ -7,6 +7,7 @@ + diff --git a/sample/EasyCaching.Demo.Providers/Startup.cs b/sample/EasyCaching.Demo.Providers/Startup.cs index 9f9e30f5..5372e2ae 100644 --- a/sample/EasyCaching.Demo.Providers/Startup.cs +++ b/sample/EasyCaching.Demo.Providers/Startup.cs @@ -61,6 +61,14 @@ public void ConfigureServices(IServiceCollection services) }); option.UseMemcached(Configuration); + + //use fasterKv + option.UseFasterKv(config => + { + // fasterKv must be set SerializerName + config.SerializerName = "msg"; + }) + .WithMessagePack("msg"); }); } @@ -84,4 +92,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF }); } } -} +} diff --git a/sample/EasyCaching.Demo.Providers/appsettings.json b/sample/EasyCaching.Demo.Providers/appsettings.json index 85b08a94..9184d1a9 100644 --- a/sample/EasyCaching.Demo.Providers/appsettings.json +++ b/sample/EasyCaching.Demo.Providers/appsettings.json @@ -76,6 +76,10 @@ "GroupId": "MyGroupId" }, "ConsumerCount":2 + }, + "fasterKv": { + "MemorySizeBit": 16, + "PageSizeBit": 15 } } } diff --git a/src/EasyCaching.Core/Internal/CachingProviderType.cs b/src/EasyCaching.Core/Internal/CachingProviderType.cs index c7ff8d1b..ed1d8f27 100644 --- a/src/EasyCaching.Core/Internal/CachingProviderType.cs +++ b/src/EasyCaching.Core/Internal/CachingProviderType.cs @@ -13,5 +13,6 @@ public enum CachingProviderType Ext1, Ext2, LiteDB, + FasterKv, } } diff --git a/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs b/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs index 4e471456..342a0ae4 100644 --- a/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs +++ b/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs @@ -53,13 +53,13 @@ public class EasyCachingConstValue /// /// The rabbitMQ Bus section. /// - public const string RabbitMQBusSection = "easycaching:rabbitmqbus"; - + public const string RabbitMQBusSection = "easycaching:rabbitmqbus"; + /// /// The kafka bus section. /// - public const string KafkaBusSection = "easycaching:kafkabus"; - + public const string KafkaBusSection = "easycaching:kafkabus"; + /// /// The zookeeper bus section. /// @@ -104,19 +104,29 @@ public class EasyCachingConstValue /// /// The default name of the serializer. /// - public const string DefaultSerializerName = "binary"; - + public const string DefaultSerializerName = "binary"; + /// /// The default name of the LiteDB. /// - public const string DefaultLiteDBName = "DefaultLiteDB"; + public const string DefaultLiteDBName = "DefaultLiteDB"; /// /// The LiteDB Bus section. - /// - public const string LiteDBSection= "easycaching:litedb"; - - public const string NotFoundCliExceptionMessage = "Can not find the matched client instance, client name is {0}"; - - public const string NotFoundSerExceptionMessage = "Can not find the matched serializer instance, serializer name is {0}"; + /// + public const string LiteDBSection= "easycaching:litedb"; + + /// + /// The default name of the FasterKv + /// + public const string DefaultFasterKvName = "DefaultFasterKvName"; + + /// + /// The FasterKv section. + /// + public const string FasterKvSection= "easycaching:fasterKv"; + + public const string NotFoundCliExceptionMessage = "Can not find the matched client instance, client name is {0}"; + + public const string NotFoundSerExceptionMessage = "Can not find the matched serializer instance, serializer name is {0}"; } } diff --git a/src/EasyCaching.FasterKv/ClientSessionPooledObjectPolicy.cs b/src/EasyCaching.FasterKv/ClientSessionPooledObjectPolicy.cs new file mode 100644 index 00000000..75cd7e7b --- /dev/null +++ b/src/EasyCaching.FasterKv/ClientSessionPooledObjectPolicy.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Concurrent; +using FASTER.core; + +namespace EasyCaching.FasterKv +{ + public class ClientSessionWrap : IDisposable + { + public ClientSession Session { get; } + + private readonly ConcurrentQueue> _innerPool; + + public ClientSessionWrap( + ClientSession clientSession, + ConcurrentQueue> innerPool) + { + Session = clientSession; + _innerPool = innerPool; + } + + public void Dispose() + { + Session.CompletePending(true); + _innerPool.Enqueue(Session); + } + } +} \ No newline at end of file diff --git a/src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptions.cs b/src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptions.cs new file mode 100644 index 00000000..325ff15f --- /dev/null +++ b/src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptions.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using EasyCaching.Core.Configurations; +using FASTER.core; + +namespace EasyCaching.FasterKv.Configurations +{ + /// + /// FasterKvCachingOptions + /// for details, see https://microsoft.github.io/FASTER/docs/fasterkv-basics/#fasterkvsettings + /// + public class FasterKvCachingOptions : BaseProviderOptions + { + /// + /// FasterKv index count, Must be power of 2 + /// + /// For example: 1024(2^10) 2048(2^11) 65536(2^16) 131072(2^17) + /// Each index is 64 bits. So this define 131072 keys. Used 1024Kb memory + public long IndexCount { get; set; } = 131072; + + /// + /// FasterKv used memory size (default: 16MB) + /// + public int MemorySizeBit { get; set; } = 24; + + /// + /// FasterKv page size (default: 1MB) + /// + public int PageSizeBit { get; set; } = 20; + + /// + /// FasterKv read cache used memory size (default: 16MB) + /// + public int ReadCacheMemorySizeBit { get; set; } = 24; + + /// + /// FasterKv read cache page size (default: 16MB) + /// + public int ReadCachePageSizeBit { get; set; } = 20; + + /// + /// FasterKv commit logs path + /// + public string LogPath { get; set; } = +#if (NET6_0 || NET7_0) + Path.Combine(Environment.CurrentDirectory, $"EasyCaching-FasterKv-{Environment.ProcessId}"); +#else + Path.Combine(Environment.CurrentDirectory, $"EasyCaching-FasterKv-{System.Diagnostics.Process.GetCurrentProcess().Id}"); +#endif + + + /// + /// Set Custom Store + /// + public FasterKV? CustomStore { get; set; } + + internal LogSettings GetLogSettings(string name) + { + return new LogSettings + { + LogDevice = Devices.CreateLogDevice(Path.Combine(LogPath, name), + preallocateFile: true, + deleteOnClose: true), + PageSizeBits = PageSizeBit, + MemorySizeBits = MemorySizeBit, + ReadCacheSettings = new ReadCacheSettings + { + MemorySizeBits = ReadCacheMemorySizeBit, + PageSizeBits = ReadCachePageSizeBit, + } + }; + } + } +} \ No newline at end of file diff --git a/src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptionsExtensions.cs b/src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptionsExtensions.cs new file mode 100644 index 00000000..d1d770da --- /dev/null +++ b/src/EasyCaching.FasterKv/Configurations/FasterKvCachingOptionsExtensions.cs @@ -0,0 +1,68 @@ +using System; +using EasyCaching.Core; +using EasyCaching.Core.Configurations; +using EasyCaching.FasterKv; +using EasyCaching.FasterKv.Configurations; +using Microsoft.Extensions.Configuration; +// ReSharper disable CheckNamespace + +namespace Microsoft.Extensions.DependencyInjection; + +public static class FasterKvCachingOptionsExtensions +{ + /// + /// Uses the FasterKv provider (specify the config via hard code). + /// + /// Options. + /// Configure provider settings. + /// The name of this provider instance. + public static EasyCachingOptions UseFasterKv( + this EasyCachingOptions options, + Action configure, + string name = EasyCachingConstValue.DefaultFasterKvName + ) + { + ArgumentCheck.NotNull(configure, nameof(configure)); + + options.RegisterExtension(new FasterKvOptionsExtension(name, configure)); + return options; + } + + /// + /// Uses the FasterKv provider (read config from configuration file). + /// + /// Options. + /// The configuration. + /// The name of this provider instance. + /// The section name in the configuration file. + public static EasyCachingOptions UseFasterKv( + this EasyCachingOptions options, + IConfiguration configuration, + string name = EasyCachingConstValue.DefaultFasterKvName, + string sectionName = EasyCachingConstValue.FasterKvSection + ) + { + var dbConfig = configuration.GetSection(sectionName); + var fasterKvOptions = new FasterKvCachingOptions(); + dbConfig.Bind(fasterKvOptions); + + void Configure(FasterKvCachingOptions x) + { + x.EnableLogging = fasterKvOptions.EnableLogging; + x.MaxRdSecond = fasterKvOptions.MaxRdSecond; + x.LockMs = fasterKvOptions.LockMs; + x.SleepMs = fasterKvOptions.SleepMs; + x.SerializerName = fasterKvOptions.SerializerName; + x.CacheNulls = fasterKvOptions.CacheNulls; + x.IndexCount = fasterKvOptions.IndexCount; + x.MemorySizeBit = fasterKvOptions.MemorySizeBit; + x.PageSizeBit = fasterKvOptions.PageSizeBit; + x.ReadCacheMemorySizeBit = fasterKvOptions.ReadCacheMemorySizeBit; + x.ReadCachePageSizeBit = fasterKvOptions.ReadCachePageSizeBit; + x.LogPath = fasterKvOptions.LogPath; + } + + options.RegisterExtension(new FasterKvOptionsExtension(name, Configure)); + return options; + } +} \ No newline at end of file diff --git a/src/EasyCaching.FasterKv/Configurations/FasterKvOptionsExtension.cs b/src/EasyCaching.FasterKv/Configurations/FasterKvOptionsExtension.cs new file mode 100644 index 00000000..5f956666 --- /dev/null +++ b/src/EasyCaching.FasterKv/Configurations/FasterKvOptionsExtension.cs @@ -0,0 +1,61 @@ +using System; +using EasyCaching.Core; +using EasyCaching.Core.Configurations; +using EasyCaching.Core.Serialization; +using EasyCaching.FasterKv.Configurations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace EasyCaching.FasterKv +{ + /// + /// FasterKv options extension. + /// + internal sealed class FasterKvOptionsExtension : IEasyCachingOptionsExtension + { + /// + /// The name. + /// + private readonly string _name; + + /// + /// The configure. + /// + private readonly Action _configure; + + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// Configure. + public FasterKvOptionsExtension(string name, Action configure) + { + _name = name; + _configure = configure; + } + + /// + /// Adds the services. + /// + /// Services. + public void AddServices(IServiceCollection services) + { + services.AddOptions(); + + services.Configure(_name, _configure); + + services.TryAddSingleton(); + + services.AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(_name); + var factory = x.GetService(); + var serializers = x.GetServices(); + return new DefaultFasterKvCachingProvider(_name, options, serializers, factory); + }); + } + } +} diff --git a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs new file mode 100644 index 00000000..80393de0 --- /dev/null +++ b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using EasyCaching.Core; +using Microsoft.Extensions.Logging; + +namespace EasyCaching.FasterKv +{ + public partial class DefaultFasterKvCachingProvider + { + public override async Task BaseExistsAsync(string cacheKey, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + using var session = GetSession(); + var result = (await session.Session.ReadAsync(GetSpanByte(cacheKey), token: cancellationToken)).Complete(); + return result.status.Found; + } + + + public override async Task BaseFlushAsync(CancellationToken cancellationToken = default) + { + EnsureNotDispose(); + using var session = GetSession(); + using var iter = session.Session.Iterate(); + while (iter.GetNext(out var recordInfo)) + { + await session.Session.DeleteAsync(ref iter.GetKey(), token: cancellationToken).ConfigureAwait(false); + } + } + + + public override async Task>> BaseGetAllAsync(IEnumerable cacheKeys, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + EnsureNotDispose(); + + using var session = GetSession(); + var dic = new Dictionary>(); + foreach (var cacheKey in cacheKeys) + { + dic[cacheKey] = await BaseGetInternalAsync(session, cacheKey, cancellationToken); + } + + return dic; + } + + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, + TimeSpan expiration, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + EnsureNotDispose(); + + using var session = GetSession(); + var result = await BaseGetInternalAsync(session, cacheKey, cancellationToken); + if (result.HasValue) + { + return result; + } + + var item = await dataRetriever(); + if (item is not null || _options.CacheNulls) + { + await SetAsync(cacheKey, item, expiration, cancellationToken); + return new CacheValue(item, true); + } + + return CacheValue.NoValue; + } + + public override async Task BaseGetAsync(string cacheKey, Type type, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + EnsureNotDispose(); + + using var session = GetSession(); + var result = await BaseGetAsync(cacheKey, cancellationToken); + return Convert.ChangeType(result.Value, type); + } + + public override async Task> BaseGetAsync(string cacheKey, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + EnsureNotDispose(); + + using var session = GetSession(); + return await BaseGetInternalAsync(session, cacheKey, cancellationToken); + } + + public override async Task BaseRemoveAllAsync(IEnumerable cacheKeys, CancellationToken cancellation = default) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + EnsureNotDispose(); + + using var session = GetSession(); + foreach (var cacheKey in cacheKeys) + { + await session.Session.DeleteAsync(GetSpanByte(cacheKey), token: cancellation).ConfigureAwait(false); + } + } + + public override async Task BaseRemoveAsync(string cacheKey, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + EnsureNotDispose(); + + using var session = GetSession(); + await session.Session.DeleteAsync(GetSpanByte(cacheKey), token: cancellationToken).ConfigureAwait(false); + } + + + public override async Task BaseSetAllAsync(IDictionary values, TimeSpan expiration, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); + EnsureNotDispose(); + + using var session = GetSession(); + foreach (var kp in values) + { + await BaseSetInternalAsync(session, kp.Key, kp.Value, cancellationToken); + } + } + + public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + EnsureNotDispose(); + + using var session = GetSession(); + await BaseSetInternalAsync(session, cacheKey, cacheValue, cancellationToken); + } + + public override async Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration, + CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + EnsureNotDispose(); + + using var session = GetSession(); + var result = await BaseGetAsync(cacheKey, cancellationToken); + if (result.HasValue == false) + { + await BaseSetInternalAsync(session, cacheKey, cacheValue, cancellationToken); + return true; + } + + return false; + } + + private async Task BaseSetInternalAsync(ClientSessionWrap sessionWarp, string cacheKey, T cacheValue, + CancellationToken cancellationToken) + { + _ = await (await sessionWarp.Session.UpsertAsync(GetSpanByte(cacheKey), + GetSpanByte(cacheValue), token: cancellationToken) + .ConfigureAwait(false)).CompleteAsync(cancellationToken); + } + + + private async Task> BaseGetInternalAsync(ClientSessionWrap session, string cacheKey, CancellationToken cancellationToken) + { + var result = (await session.Session.ReadAsync(GetSpanByte(cacheKey), + token: cancellationToken) + .ConfigureAwait(false)).Complete(); + if (result.status.Found) + { + if (_options.EnableLogging) + _logger?.LogInformation("Cache Hit : cacheKey = {CacheKey}", cacheKey); + + CacheStats.OnHit(); + + return new CacheValue(GetTValue(ref result.output), true); + } + + CacheStats.OnMiss(); + if (_options.EnableLogging) + _logger?.LogInformation("Cache Missed : cacheKey = {CacheKey}", cacheKey); + + return CacheValue.NoValue; + } + + // Operations not currently supported by FasterKv + #region NotSupprotOperate + public override Task>> BaseGetByPrefixAsync(string prefix, + CancellationToken cancellationToken = default) + { + throw new NotSupportedException("BaseGetByPrefixAsync is not supported in FasterKv provider."); + } + + public override Task BaseGetCountAsync(string prefix = "", CancellationToken cancellationToken = default) + { + throw new NotSupportedException("BaseGetCountAsync is not supported in FasterKv provider."); + } + + public override Task BaseRemoveByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException("BaseRemoveByPrefixAsync is not supported in FasterKv provider."); + } + + public override Task BaseRemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default) + { + throw new NotSupportedException("BaseRemoveByPatternAsync is not supported in FasterKv provider."); + } + + public override Task BaseGetExpirationAsync(string cacheKey, + CancellationToken cancellationToken = default) + { + throw new NotSupportedException("BaseGetExpirationAsync is not supported in FasterKv provider."); + } + #endregion + } +} \ No newline at end of file diff --git a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs new file mode 100644 index 00000000..f2e2e840 --- /dev/null +++ b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs @@ -0,0 +1,377 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using EasyCaching.Core; +using EasyCaching.Core.Serialization; +using EasyCaching.FasterKv.Configurations; +using FASTER.core; +using Microsoft.Extensions.Logging; + +namespace EasyCaching.FasterKv +{ + public sealed partial class DefaultFasterKvCachingProvider : EasyCachingAbstractProvider, IDisposable + { + // name + private readonly string _name; + private bool _disposed; + + // com + private readonly ILogger? _logger; + private readonly IEasyCachingSerializer _serializer; + private readonly FasterKvCachingOptions _options; + + // faster + private readonly FasterKV _fasterKv; + private readonly IDevice? _logDevice; + private readonly + ConcurrentQueue> _sessionPool; + + /// + /// The cache stats. + /// + private readonly CacheStats _cacheStats; + + private readonly ProviderInfo _info; + + ~DefaultFasterKvCachingProvider() + { + Dispose(false); + } + + public DefaultFasterKvCachingProvider( + string name, + FasterKvCachingOptions options, + IEnumerable serializers, + ILoggerFactory? loggerFactory = null) + { + ArgumentCheck.NotNull(options, nameof(options)); + ArgumentCheck.NotNull(serializers, nameof(serializers)); + + _name = name; + + _options = options; + _logger = loggerFactory?.CreateLogger(); + + if (_options.CustomStore is null) + { + var logSetting = options.GetLogSettings(name); + _logDevice = logSetting.LogDevice; + _fasterKv = new FasterKV(_options.IndexCount, logSetting, + loggerFactory: loggerFactory); + } + else + { + _fasterKv = _options.CustomStore; + } + + _sessionPool = + new ConcurrentQueue>(); + + var serName = !string.IsNullOrWhiteSpace(options.SerializerName) ? options.SerializerName : name; + _serializer = serializers.FirstOrDefault(x => x.Name.Equals(serName)) ?? + throw new EasyCachingNotFoundException(string.Format( + EasyCachingConstValue.NotFoundSerExceptionMessage, + serName)); + + _cacheStats = new CacheStats(); + ProviderName = _name; + ProviderType = CachingProviderType.FasterKv; + ProviderStats = _cacheStats; + ProviderMaxRdSecond = _options.MaxRdSecond; + IsDistributedProvider = false; + _info = new ProviderInfo + { + CacheStats = _cacheStats, + EnableLogging = options.EnableLogging, + IsDistributedProvider = IsDistributedProvider, + LockMs = options.LockMs, + MaxRdSecond = options.MaxRdSecond, + ProviderName = ProviderName, + ProviderType = ProviderType, + SerializerName = options.SerializerName, + SleepMs = options.SleepMs, + CacheNulls = options.CacheNulls + }; + } + + public override object BaseGetDatabase() + { + EnsureNotDispose(); + return _fasterKv; + } + + public override bool BaseExists(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + EnsureNotDispose(); + + using var sessionWarp = GetSession(); + var key = GetSpanByte(cacheKey); + var result = sessionWarp.Session.Read(key); + if (result.status.IsPending) + sessionWarp.Session.CompletePending(true); + return result.status.Found; + } + + + public override void BaseFlush() + { + EnsureNotDispose(); + using var session = GetSession(); + using var iter = session.Session.Iterate(); + while (iter.GetNext(out var recordInfo)) + { + session.Session.Delete(ref iter.GetKey()); + } + } + + + public override CacheValue BaseGet(string cacheKey, Func dataRetriever, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + EnsureNotDispose(); + + using var session = GetSession(); + var result = BaseGetInternal(cacheKey, session); + if (result.HasValue) + { + return result; + } + + var item = dataRetriever(); + if (item is not null || _options.CacheNulls) + { + Set(cacheKey, item, expiration); + return new CacheValue(item, true); + } + + return CacheValue.NoValue; + } + + public override CacheValue BaseGet(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + EnsureNotDispose(); + + using var session = GetSession(); + return BaseGetInternal(cacheKey, session); + } + + public override IDictionary> BaseGetAll(IEnumerable cacheKeys) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + EnsureNotDispose(); + + using var session = GetSession(); + var dic = new Dictionary>(); + foreach (var cacheKey in cacheKeys) + { + dic[cacheKey] = BaseGetInternal(cacheKey, session); + } + + return dic; + } + + + public override void BaseRemove(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + EnsureNotDispose(); + + using var session = GetSession(); + // ignore result + _ = session.Session.Delete(GetSpanByte(cacheKey)); + } + + public override void BaseRemoveAll(IEnumerable cacheKeys) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + EnsureNotDispose(); + + using var session = GetSession(); + foreach (var cacheKey in cacheKeys) + { + _ = session.Session.Delete(GetSpanByte(cacheKey)); + } + } + + + public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + EnsureNotDispose(); + + using var sessionWarp = GetSession(); + BaseSetInternal(sessionWarp, cacheKey, cacheValue); + } + + public override void BaseSetAll(IDictionary values, TimeSpan expiration) + { + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); + + using var session = GetSession(); + foreach (var kp in values) + { + BaseSetInternal(session, kp.Key, kp.Value); + } + } + + public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + EnsureNotDispose(); + + using var session = GetSession(); + var result = BaseGet(cacheKey); + if (result.HasValue == false) + { + BaseSetInternal(session, cacheKey, cacheValue); + return true; + } + + return false; + } + + + public override ProviderInfo BaseGetProviderInfo() + { + return _info; + } + + + + + private CacheValue BaseGetInternal(string cacheKey, ClientSessionWrap session) + { + var context = new StoreContext(); + var result = session.Session.Read(GetSpanByte(cacheKey), context); + if (result.status.IsPending) + { + session.Session.CompletePending(true); + context.FinalizeRead(out result.status, out result.output); + } + + if (result.status.Found) + { + CacheStats.OnHit(); + if (_options.EnableLogging) + _logger?.LogInformation("Cache Hit : cacheKey = {CacheKey}", cacheKey); + var value = GetTValue(ref result.output); + return new CacheValue(value, true); + } + + CacheStats.OnMiss(); + if (_options.EnableLogging) + _logger?.LogInformation("Cache Missed : cacheKey = {CacheKey}", cacheKey); + + return CacheValue.NoValue; + } + + private void BaseSetInternal(ClientSessionWrap sessionWarp, string cacheKey, T cacheValue) + { + var key = GetSpanByte(cacheKey); + var value = GetSpanByte(cacheValue); + _ = sessionWarp.Session.Upsert(key, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private SpanByte GetSpanByte(T value) + { + var bytes = _serializer.Serialize(value); + bytes.AsSpan().GetPinnableReference(); + return SpanByte.FromFixedSpan(bytes); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T GetTValue(ref SpanByteAndMemory span) + { + return _serializer.Deserialize(span.Memory.Memory.ToArray()); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureNotDispose() + { + if (_disposed) throw new ObjectDisposedException(nameof(DefaultFasterKvCachingProvider)); + } + + /// + /// Get ClientSession from pool + /// + /// + private ClientSessionWrap GetSession() + { + if (_sessionPool.TryDequeue(out var session) == false) + { + session = _fasterKv.For(new StoreFunctions()).NewSession(); + } + + return new ClientSessionWrap(session, _sessionPool); + } + + // Operations not currently supported by FasterKv + #region NotSupprotOperate + + public override TimeSpan BaseGetExpiration(string cacheKey) + { + throw new NotSupportedException("BaseGetExpiration is not supported in FasterKv provider."); + } + + public override IDictionary> BaseGetByPrefix(string prefix) + { + throw new NotSupportedException("BaseGetByPrefix is not supported in FasterKv provider."); + } + + public override int BaseGetCount(string prefix = "") + { + throw new NotSupportedException("BaseGetCount is not supported in FasterKv provider."); + } + + public override void BaseRemoveByPrefix(string prefix) + { + throw new NotSupportedException("BaseRemoveByPrefix is not supported in FasterKv provider."); + } + + public override void BaseRemoveByPattern(string pattern) + { + throw new NotSupportedException("BaseRemoveByPattern is not supported in FasterKv provider."); + } + + #endregion + + private void Dispose(bool _) + { + if (_disposed) + return; + + foreach (var session in _sessionPool) + { + session.Dispose(); + } + + _logDevice?.Dispose(); + if (_options.CustomStore != _fasterKv) + { + _fasterKv.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj b/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj new file mode 100644 index 00000000..99c28038 --- /dev/null +++ b/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0;net6.0 + 10 + enable + true + + + + + + + + + + + diff --git a/src/EasyCaching.FasterKv/StoreFunctions.cs b/src/EasyCaching.FasterKv/StoreFunctions.cs new file mode 100644 index 00000000..f7b8e9be --- /dev/null +++ b/src/EasyCaching.FasterKv/StoreFunctions.cs @@ -0,0 +1,34 @@ +using FASTER.core; + +namespace EasyCaching.FasterKv; + +public class StoreContext +{ + private Status _status; + private SpanByteAndMemory _output; + + internal void Populate(ref Status status, ref SpanByteAndMemory output) + { + _status = status; + _output = output; + } + + internal void FinalizeRead(out Status status, out SpanByteAndMemory output) + { + status = _status; + output = _output; + } +} + +public class StoreFunctions : SpanByteFunctions +{ + public override void ReadCompletionCallback(ref SpanByte key, + ref SpanByte input, + ref SpanByteAndMemory output, + StoreContext ctx, + Status status, + RecordMetadata recordMetadata) + { + ctx?.Populate(ref status, ref output); + } +} \ No newline at end of file diff --git a/test/EasyCaching.PerformanceTests/EasyCaching.PerformanceTests.csproj b/test/EasyCaching.PerformanceTests/EasyCaching.PerformanceTests.csproj index 3197bf99..cf989173 100644 --- a/test/EasyCaching.PerformanceTests/EasyCaching.PerformanceTests.csproj +++ b/test/EasyCaching.PerformanceTests/EasyCaching.PerformanceTests.csproj @@ -7,8 +7,8 @@ - - + + @@ -19,6 +19,8 @@ + + diff --git a/test/EasyCaching.PerformanceTests/FasterKvBenchmark.cs b/test/EasyCaching.PerformanceTests/FasterKvBenchmark.cs new file mode 100644 index 00000000..05e00ece --- /dev/null +++ b/test/EasyCaching.PerformanceTests/FasterKvBenchmark.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Order; +using EasyCaching.Core; +using EasyCaching.FasterKv; +using EasyCaching.SQLite; +using Microsoft.Extensions.DependencyInjection; + +namespace EasyCaching.PerformanceTests; + +public enum TestType +{ + Read, + Write, + Random +} + +[GcForce] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +[SimpleJob(RuntimeMoniker.Net60, launchCount: 1, warmupCount: 5, targetCount: 10)] +[MemoryDiagnoser] +public class FasterKvBenchmark +{ + private const long Count = 1000; + private static readonly Random _random = new Random(1024); + private IEasyCachingProvider _provider; + private static readonly TimeSpan _default = TimeSpan.FromSeconds(30); + + [Params("sqlite","fasterKv")] + public string Provider { get; set; } + + [Params(TestType.Read, TestType.Write, TestType.Random)] + public TestType Type { get; set; } + + [Params(1,4,8)] + public int ThreadCount { get; set; } + + private static readonly string[] HotKeys = Enumerable.Range(0, (int)(Count * 0.5)) + .Select(i => $"cache_{_random.Next(0, (int) Count)}") + .ToArray(); + + [GlobalSetup] + public void Setup() + { + var services = new ServiceCollection(); + services.AddEasyCaching(x => + { + if (Provider == "fasterKv") + { + x.UseFasterKv(options => { options.SerializerName = "msg"; }) + .WithMessagePack("msg"); + } + else + { + x.UseSQLite(config => + { + config.DBConfig = new SQLiteDBOptions { FileName = "my.db" }; + }); + } + } + ); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + _provider = serviceProvider.GetService(); + + switch (Type) + { + case TestType.Write: + break; + case TestType.Read: + case TestType.Random: + for (int i = 0; i < Count; i++) + { + _provider!.Set($"cache_{i}", "cache", _default); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + [Benchmark] + public async Task Full() + { + var tasks = new Task[ThreadCount]; + var threadOpCount = (int)(HotKeys.Length / ThreadCount); + for (int i = 0; i < ThreadCount; i++) + { + int i1 = i; + tasks[i] = Task.Run(() => + { + var j = i1 * threadOpCount; + switch (Type) + { + case TestType.Read: + for (; j < threadOpCount; j++) + { + _provider.Get(HotKeys[j]); + } + + break; + case TestType.Write: + for (; j < threadOpCount; j++) + { + _provider.Set(HotKeys[j], "cache", _default); + } + + break; + case TestType.Random: + for (; j < threadOpCount; j++) + { + if (j % 2 == 0) + { + _provider.Get(HotKeys[j]); + } + else + { + _provider.Set(HotKeys[j], "cache", _default); + } + } + + break; + } + }); + } + + await Task.WhenAll(tasks); + } + + + [GlobalCleanup] + public void Cleanup() + { + if (_provider is DefaultFasterKvCachingProvider fasterKv) + { + fasterKv.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/EasyCaching.PerformanceTests/Program.cs b/test/EasyCaching.PerformanceTests/Program.cs index 308ffafe..21ece500 100644 --- a/test/EasyCaching.PerformanceTests/Program.cs +++ b/test/EasyCaching.PerformanceTests/Program.cs @@ -10,8 +10,9 @@ static void Main(string[] args) //BenchmarkRunner.Run(); //BenchmarkRunner.Run(); //BenchmarkRunner.Run(); - BenchmarkRunner.Run(); + //BenchmarkRunner.Run(); //BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } } diff --git a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs index ce00bdf9..9b359f10 100644 --- a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs @@ -246,7 +246,7 @@ public async Task Refresh_Async_Should_Throw_ArgumentOutOfRangeException_When_Ex [InlineData("")] [InlineData(" ")] [InlineData(null)] - public void RemoveByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNullOrWhiteSpace(string prefix) + public virtual void RemoveByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNullOrWhiteSpace(string prefix) { Assert.Throws(() => _provider.RemoveByPrefix(prefix)); } @@ -255,7 +255,7 @@ public void RemoveByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNu [InlineData("")] [InlineData(" ")] [InlineData(null)] - public async Task RemoveByPrefix_Async_Should_Throw_ArgumentNullException_When_Prefix_IsNullOrWhiteSpace(string preifx) + public async virtual Task RemoveByPrefix_Async_Should_Throw_ArgumentNullException_When_Prefix_IsNullOrWhiteSpace(string preifx) { await Assert.ThrowsAsync(async () => await _provider.RemoveByPrefixAsync(preifx)); } @@ -264,7 +264,7 @@ public async Task RemoveByPrefix_Async_Should_Throw_ArgumentNullException_When_P [InlineData("")] [InlineData(" ")] [InlineData(null)] - public void GetAllByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNullOrWhiteSpace(string prefix) + public virtual void GetAllByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNullOrWhiteSpace(string prefix) { Assert.Throws(() => _provider.RemoveByPrefix(prefix)); } @@ -273,7 +273,7 @@ public void GetAllByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNu [InlineData("")] [InlineData(" ")] [InlineData(null)] - public async Task GetAllByPrefix_Async_Should_Throw_ArgumentNullException_When_Prefix_IsNullOrWhiteSpace(string preifx) + public virtual async Task GetAllByPrefix_Async_Should_Throw_ArgumentNullException_When_Prefix_IsNullOrWhiteSpace(string preifx) { await Assert.ThrowsAsync(async () => await _provider.RemoveByPrefixAsync(preifx)); } diff --git a/test/EasyCaching.UnitTests/CachingTests/FasterKvCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/FasterKvCachingProviderTest.cs new file mode 100644 index 00000000..d16ecf8c --- /dev/null +++ b/test/EasyCaching.UnitTests/CachingTests/FasterKvCachingProviderTest.cs @@ -0,0 +1,164 @@ +using System; +using System.Threading.Tasks; +using EasyCaching.Core; +using EasyCaching.Core.Configurations; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace EasyCaching.UnitTests.CachingTests; + +public class FasterKvCachingProviderTest : BaseCachingProviderTest +{ + private readonly ITestOutputHelper _testOutputHelper; + + public FasterKvCachingProviderTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _defaultTs = TimeSpan.FromSeconds(30); + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) + { + var services = new ServiceCollection(); + services.AddEasyCaching(x => + x.UseFasterKv(options => + { + options.SerializerName = "msg"; + additionalSetup(options); + }) + .WithMessagePack("msg") + ); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + return serviceProvider.GetService(); + } + + [Fact] + protected void Set_And_Get_Big_DataSet_Should_Succeed() + { + // set 10w key + for (int i = 0; i < 10_0000; i++) + { + _provider.Set($"Key_{i}", $"Cache_{i}", _defaultTs); + } + + for (int i = 0; i < 10_0000; i++) + { + var value = _provider.Get($"Key_{i}"); + Assert.True(value.HasValue); + Assert.Equal(value.Value, $"Cache_{i}"); + } + } + + [Fact] + protected async Task SetAsync_And_GetAsync_Big_DataSet_Should_Succeed() + { + // set 10w key + for (int i = 0; i < 10_0000; i++) + { + await _provider.SetAsync($"Key_{i}", $"Cache_{i}", _defaultTs); + } + + for (int i = 0; i < 10_0000; i++) + { + var value = await _provider.GetAsync($"Key_{i}"); + Assert.True(value.HasValue); + Assert.Equal(value.Value, $"Cache_{i}"); + } + } + + protected override Task GetAsync_Parallel_Should_Succeed() + { + return Task.FromResult(1); + } + + protected override void Get_Parallel_Should_Succeed() + { + } + + public override Task GetAllByPrefix_Async_Should_Throw_ArgumentNullException_When_Prefix_IsNullOrWhiteSpace( + string preifx) + { + return Task.CompletedTask; + } + + public override void GetAllByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNullOrWhiteSpace( + string prefix) + { + } + + protected override Task Get_Count_Async_With_Prefix_Should_Succeed() + { + return Task.CompletedTask; + } + + protected override Task Get_Count_Async_Without_Prefix_Should_Succeed() + { + return Task.CompletedTask; + } + + protected override void Get_Count_With_Prefix_Should_Succeed() + { + } + + protected override void Get_Count_Without_Prefix_Should_Succeed() + { + } + + protected override void GetByPrefix_Should_Succeed() + { + } + + protected override void GetByPrefix_With_Not_Existed_Prefix_Should_Return_Empty_Dict() + { + } + + protected override Task GetByPrefixAsync_Should_Succeed() + { + return Task.CompletedTask; + } + + protected override Task GetByPrefixAsync_With_Not_Existed_Prefix_Should_Return_Empty_Dict() + { + return Task.CompletedTask; + } + + protected override Task GetExpiration_Async_Should_Succeed() + { + return Task.CompletedTask; + } + + protected override void GetExpiration_Should_Succeed() + { + } + + public override void RemoveByPattern_Should_Succeed() + { + } + + public override Task RemoveByPatternAsync_Should_Succeed() + { + return Task.CompletedTask; + } + + public override Task RemoveByPrefix_Async_Should_Throw_ArgumentNullException_When_Prefix_IsNullOrWhiteSpace( + string preifx) + + { + return Task.CompletedTask; + } + + protected override void RemoveByPrefix_Should_Succeed() + { + } + + public override void RemoveByPrefix_Should_Throw_ArgumentNullException_When_CacheKey_IsNullOrWhiteSpace( + string prefix) + { + } + + protected override Task RemoveByPrefixAsync_Should_Succeed() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj index e86532c4..26b51b1d 100644 --- a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj +++ b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj @@ -17,7 +17,7 @@ - + @@ -40,6 +40,7 @@ + From 23540d17d063945fa9028ce4f25a8db7706c9c7f Mon Sep 17 00:00:00 2001 From: Thomas Sarmis Date: Thu, 24 Nov 2022 15:35:26 +0200 Subject: [PATCH 05/12] fix: use DefaultObjectPoolProvider to get a DisposableObjectPool instead of a DefaultObjectPool (#427) Default object pool does not dispose non-retained object, leading to IModel leakage. Co-authored-by: t.sarmis --- bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs b/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs index 72e6ad84..973cc103 100644 --- a/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs +++ b/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs @@ -70,8 +70,10 @@ IPooledObjectPolicy _objectPolicy _subConnection = factory.CreateConnection(); - _pubChannelPool = new DefaultObjectPool(_objectPolicy); - + var provider = new DefaultObjectPoolProvider(); + + _pubChannelPool = provider.Create(_objectPolicy); + _busId = Guid.NewGuid().ToString("N"); BusName = "easycachingbus"; From be302f9260e6a592ac22f55580323872abbea3b0 Mon Sep 17 00:00:00 2001 From: Thomas Sarmis Date: Mon, 28 Nov 2022 13:59:54 +0200 Subject: [PATCH 06/12] feat: restore queue model and consumer on disconnection (#428) Co-authored-by: t.sarmis --- .../DefaultRabbitMQBus.cs | 57 ++++++++++++++----- .../Bus/IEasyCachingSubscriber.cs | 3 +- .../Bus/NullEasyCachingBus.cs | 5 +- .../EasyCachingAbstractBus.cs | 10 +++- .../Configurations/HybridCachingOptions.cs | 8 +++ .../HybridCachingProvider.cs | 17 +++++- test/EasyCaching.UnitTests/Fake/FakeBus.cs | 2 +- 7 files changed, 83 insertions(+), 19 deletions(-) diff --git a/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs b/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs index 973cc103..5ef8a1a2 100644 --- a/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs +++ b/bus/EasyCaching.Bus.RabbitMQ/DefaultRabbitMQBus.cs @@ -150,20 +150,51 @@ public override void BaseSubscribe(string topic, Action acti queueName = _options.QueueName; } - Task.Factory.StartNew(() => + Task.Factory.StartNew( + () => StartConsumer(queueName, topic), + TaskCreationOptions.LongRunning); + } + + + private void StartConsumer(string queueName, string topic) + { + var model = _subConnection.CreateModel(); + + model.ExchangeDeclare(_options.TopicExchangeName, ExchangeType.Topic, true, false, null); + model.QueueDeclare(queueName, false, false, true, null); + // bind the queue with the exchange. + model.QueueBind(queueName, _options.TopicExchangeName, topic); + var consumer = new EventingBasicConsumer(model); + consumer.Received += OnMessage; + consumer.Shutdown += (sender, e) => { - var model = _subConnection.CreateModel(); - model.ExchangeDeclare(_options.TopicExchangeName, ExchangeType.Topic, true, false, null); - model.QueueDeclare(queueName, false, false, true, null); - // bind the queue with the exchange. - model.QueueBind(queueName, _options.TopicExchangeName, topic); - var consumer = new EventingBasicConsumer(model); - consumer.Received += OnMessage; - consumer.Shutdown += OnConsumerShutdown; - - model.BasicConsume(queueName, true, consumer); - - }, TaskCreationOptions.LongRunning); + OnConsumerShutdown(sender, e); + OnConsumerError(queueName, topic, model); + }; + + consumer.ConsumerCancelled += (s, e) => + { + OnConsumerError(queueName, topic, model); + }; + + model.BasicConsume(queueName, true, consumer); + } + + private void OnConsumerError(string queueName, string topic, IModel model) + { + StartConsumer(queueName, topic); + BaseOnReconnect(); + try + { + if (model?.IsOpen == true) + { + model?.Dispose(); + } + } + catch + { + // nothing to do + } } /// diff --git a/src/EasyCaching.Core/Bus/IEasyCachingSubscriber.cs b/src/EasyCaching.Core/Bus/IEasyCachingSubscriber.cs index 768005ce..24fc9eff 100644 --- a/src/EasyCaching.Core/Bus/IEasyCachingSubscriber.cs +++ b/src/EasyCaching.Core/Bus/IEasyCachingSubscriber.cs @@ -12,6 +12,7 @@ public interface IEasyCachingSubscriber /// /// Topic. /// Action. - void Subscribe(string topic, Action action); + /// Reconnect Action. + void Subscribe(string topic, Action action, Action reconnectAction = null); } } diff --git a/src/EasyCaching.Core/Bus/NullEasyCachingBus.cs b/src/EasyCaching.Core/Bus/NullEasyCachingBus.cs index a64a16e8..f58f6574 100644 --- a/src/EasyCaching.Core/Bus/NullEasyCachingBus.cs +++ b/src/EasyCaching.Core/Bus/NullEasyCachingBus.cs @@ -26,7 +26,7 @@ public class NullEasyCachingBus : IEasyCachingBus /// so the garbage collector can reclaim the memory that /// the was occupying. public void Dispose() { } - + /// /// Publish the specified topic and message. /// @@ -54,7 +54,8 @@ public void Publish(string topic, EasyCachingMessage message) /// /// Topic. /// Action. - public void Subscribe(string topic, Action action) + /// Reconnect Action. + public void Subscribe(string topic, Action action, Action reconnectAction = null) { } diff --git a/src/EasyCaching.Core/EasyCachingAbstractBus.cs b/src/EasyCaching.Core/EasyCachingAbstractBus.cs index 5031bd27..28d7a25a 100644 --- a/src/EasyCaching.Core/EasyCachingAbstractBus.cs +++ b/src/EasyCaching.Core/EasyCachingAbstractBus.cs @@ -18,6 +18,8 @@ public abstract class EasyCachingAbstractBus : IEasyCachingBus protected Action _handler; + protected Action _reconnectHandler; + protected string BusName { get; set; } public string Name => this.BusName; @@ -74,9 +76,10 @@ public void Publish(string topic, EasyCachingMessage message) } } - public void Subscribe(string topic, Action action) + public void Subscribe(string topic, Action action, Action reconnectAction) { _handler = action; + _reconnectHandler = reconnectAction; BaseSubscribe(topic, action); } @@ -105,5 +108,10 @@ public virtual void BaseOnMessage(EasyCachingMessage message) } } } + + public virtual void BaseOnReconnect() + { + _reconnectHandler?.Invoke(); + } } } diff --git a/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs b/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs index 0840f493..83608b9a 100644 --- a/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs +++ b/src/EasyCaching.HybridCache/Configurations/HybridCachingOptions.cs @@ -43,5 +43,13 @@ public class HybridCachingOptions /// When sending message failed, we will retry some times, default is 3 times. /// public int BusRetryCount { get; set; } = 3; + + /// + /// Flush the local cache on bus disconnection/reconnection + /// + /// + /// Flushing the local cache will avoid using stale data but may cause app jitters until the local cache get's re-populated. + /// + public bool FlushLocalCacheOnBusReconnection { get; set; } = false; } } diff --git a/src/EasyCaching.HybridCache/HybridCachingProvider.cs b/src/EasyCaching.HybridCache/HybridCachingProvider.cs index e8594fa8..8183408c 100644 --- a/src/EasyCaching.HybridCache/HybridCachingProvider.cs +++ b/src/EasyCaching.HybridCache/HybridCachingProvider.cs @@ -93,7 +93,7 @@ string name else this._distributedCache = distributed; this._bus = bus ?? NullEasyCachingBus.Instance; - this._bus.Subscribe(_options.TopicName, OnMessage); + this._bus.Subscribe(_options.TopicName, OnMessage, OnReconnect); this._cacheId = Guid.NewGuid().ToString("N"); @@ -159,6 +159,21 @@ private void OnMessage(EasyCachingMessage message) } } + /// + /// On reconnect (flushes local memory as it could be stale). + /// + + private void OnReconnect() + { + if (!_options.FlushLocalCacheOnBusReconnection) + { + return; + } + + LogMessage("Flushing local cache due to bus reconnection"); + _localCache.Flush(); + } + /// /// Exists the specified cacheKey. /// diff --git a/test/EasyCaching.UnitTests/Fake/FakeBus.cs b/test/EasyCaching.UnitTests/Fake/FakeBus.cs index db96bc11..f4077c26 100644 --- a/test/EasyCaching.UnitTests/Fake/FakeBus.cs +++ b/test/EasyCaching.UnitTests/Fake/FakeBus.cs @@ -19,7 +19,7 @@ public void Publish(string topic, EasyCachingMessage message) return Task.CompletedTask; } - public void Subscribe(string topic, Action action) + public void Subscribe(string topic, Action action, Action reconnectAction = null) { } From 7b63e2687efeb34ad58bf3a61bf2659f2a2a0bdd Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Tue, 29 Nov 2022 18:35:19 -0800 Subject: [PATCH 07/12] Add GetAllKeysByPrefix to InMemory and Redis (#422) * - add getall (inMemory - redis) - add getall-keys (inMemory - resis) * refactor get-all and get-all-keys with optional prefix * add GetAllKeysByPrefix for InMemoryCache and Redis * handle base KeyPrefix from config in result GetAllKeysByPrefix --- sample/EasyCaching.Demo.ConsoleApp/Program.cs | 63 +++++++++++-------- .../DefaultCSRedisCachingProvider.Async.cs | 5 ++ .../DefaultCSRedisCachingProvider.cs | 5 ++ .../EasyCachingAbstractProvider.cs | 54 ++++++++++++++++ src/EasyCaching.Core/IEasyCachingProvider.cs | 17 ++++- .../DefaultDiskCachingProvider.Async.cs | 6 ++ .../DefaultDiskCachingProvider.cs | 5 ++ .../DefaultFasterKvCachingProvider.Async.cs | 4 ++ .../DefaultFasterKvCachingProvider.cs | 5 ++ .../DefaultInMemoryCachingProvider.Async.cs | 15 +++++ .../DefaultInMemoryCachingProvider.cs | 13 ++++ .../Internal/IInMemoryCaching.cs | 1 + .../Internal/InMemoryCaching.cs | 15 +++++ .../DefaultLiteDBCachingProvider.Async.cs | 5 ++ .../DefaultLiteDBCachingProvider.cs | 5 ++ .../DefaultMemcachedCachingProvider.Async.cs | 5 ++ .../DefaultMemcachedCachingProvider.cs | 5 ++ .../DefaultRedisCachingProvider.Async.cs | 21 +++++++ .../DefaultRedisCachingProvider.cs | 45 +++++++++++++ .../DefaultSQLiteCachingProvider.Async.cs | 5 ++ .../DefaultSQLiteCachingProvider.cs | 5 ++ .../Diagnostics/MyCachingProvider.cs | 11 ++++ .../Fake/FakeDistributedCachingProvider.cs | 13 +++- .../Fake/FakeLocalCachingProvider.cs | 13 +++- 24 files changed, 312 insertions(+), 29 deletions(-) diff --git a/sample/EasyCaching.Demo.ConsoleApp/Program.cs b/sample/EasyCaching.Demo.ConsoleApp/Program.cs index 9c345875..03f7b282 100644 --- a/sample/EasyCaching.Demo.ConsoleApp/Program.cs +++ b/sample/EasyCaching.Demo.ConsoleApp/Program.cs @@ -1,4 +1,6 @@ -namespace EasyCaching.Demo.ConsoleApp +using System.Threading.Tasks; + +namespace EasyCaching.Demo.ConsoleApp { using EasyCaching.Core; using EasyCaching.SQLite; @@ -16,13 +18,13 @@ static void Main(string[] args) { option.UseInMemory("m1"); - //option.UseRedis(config => - //{ - // config.DBConfig = new Redis.RedisDBOptions { Configuration = "localhost" }; - // config.SerializerName = "json"; - //}, "r1"); - - + // option.UseRedis(config => + // { + // config.DBConfig = new Redis.RedisDBOptions { Configuration = "localhost" }; + // config.SerializerName = "json"; + // }, "r1"); + // + option.UseSQLite(c => { c.DBConfig = new SQLiteDBOptions @@ -33,43 +35,51 @@ static void Main(string[] args) }; }, "s1"); - //option.WithJson(jsonSerializerSettingsConfigure: x => - //{ - // x.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.None; - //}, "json"); + // option.WithJson(jsonSerializerSettingsConfigure: x => + // { + // x.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.None; + // }, "json"); }); IServiceProvider serviceProvider = services.BuildServiceProvider(); var factory = serviceProvider.GetService(); - - //var redisCache = factory.GetCachingProvider("r1"); - - //redisCache.Set("rkey", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); - - //var redisVal = redisCache.Get("rkey"); - - //Console.WriteLine($"redis cache get value, {redisVal.HasValue} {redisVal.IsNull} {redisVal.Value} "); - - + + // var redisCache = factory.GetCachingProvider("r1"); + // + // redisCache.Set("rkey", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); + // + // var redisAllKey = redisCache.GetAllKeysByPrefix("rkey"); + // + // var redisVal = redisCache.Get("rkey"); + // + // Console.WriteLine($"redis cache get value, {redisVal.HasValue} {redisVal.IsNull} {redisVal.Value}"); + + + var mCache = factory.GetCachingProvider("m1"); - + mCache.Set("mkey1", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); var mVal1 = mCache.Get("mkey1"); - mCache.Set("mkey", "mvalue", TimeSpan.FromSeconds(20)); - + var mVal = mCache.Get("mkey"); - + + var mAllKey = mCache.GetAllKeysByPrefix("mk"); + Console.WriteLine($"in-memory cache get value, {mVal.HasValue} {mVal.IsNull} {mVal.Value} "); + var sCache = factory.GetCachingProvider("s1"); + sCache.Set("skey", "svalue", TimeSpan.FromSeconds(20)); + var sVal = sCache.Get("skey"); + Console.WriteLine($"sqlite cache get value, {sVal.HasValue} {sVal.IsNull} {sVal.Value} "); Console.ReadKey(); @@ -82,3 +92,4 @@ public class Product public string Name { get; set; } } } + diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs index 2135fb0f..b3c2ad34 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs @@ -64,6 +64,11 @@ public override async Task>> BaseGetAllAsync> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException(); + } + /// /// Gets the async. /// diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs index 8b36e3e5..de162c3f 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs @@ -252,6 +252,11 @@ public override IDictionary> BaseGetAll(IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + throw new NotSupportedException(); + } + /// /// Handles the prefix of CacheKey. /// diff --git a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs index 67ff62f7..b4985128 100644 --- a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs +++ b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs @@ -49,6 +49,8 @@ protected EasyCachingAbstractProvider(IDistributedLockFactory lockFactory, BaseP public abstract Task BaseFlushAsync(CancellationToken cancellationToken = default); public abstract CacheValue BaseGet(string cacheKey, Func dataRetriever, TimeSpan expiration); public abstract CacheValue BaseGet(string cacheKey); + public abstract IEnumerable BaseGetAllKeysByPrefix(string prefix); + public abstract Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default); 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); @@ -256,6 +258,58 @@ public CacheValue Get(string cacheKey) } } } + + public IEnumerable GetAllKeysByPrefix(string prefix) + { + var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(GetAllKeysByPrefix), new[] { prefix })); + Exception e = null; + try + { + return BaseGetAllKeysByPrefix(prefix); + } + catch (Exception ex) + { + e = ex; + throw; + } + finally + { + if (e != null) + { + s_diagnosticListener.WriteGetCacheError(operationId, e); + } + else + { + s_diagnosticListener.WriteGetCacheAfter(operationId); + } + } + } + + public async Task> GetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(GetAllKeysByPrefixAsync), new[] { prefix })); + Exception e = null; + try + { + return await BaseGetAllKeysByPrefixAsync(prefix); + } + catch (Exception ex) + { + e = ex; + throw; + } + finally + { + if (e != null) + { + s_diagnosticListener.WriteGetCacheError(operationId, e); + } + else + { + s_diagnosticListener.WriteGetCacheAfter(operationId); + } + } + } public IDictionary> GetAll(IEnumerable cacheKeys) { diff --git a/src/EasyCaching.Core/IEasyCachingProvider.cs b/src/EasyCaching.Core/IEasyCachingProvider.cs index 4606904b..67ae8190 100644 --- a/src/EasyCaching.Core/IEasyCachingProvider.cs +++ b/src/EasyCaching.Core/IEasyCachingProvider.cs @@ -21,8 +21,23 @@ public interface IEasyCachingProvider : IEasyCachingProviderBase /// /// true if is use lock; otherwise, false. bool UseLock { get; } + + /// + /// Gets all keys by prefix. + /// + /// Prefix. + /// The all keys by prefix. + IEnumerable GetAllKeysByPrefix(string prefix); + + /// + /// Gets all keys by prefix async. + /// + /// Prefix. + /// CancellationToken + /// The all keys by prefix async. + Task> GetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default); - /// + /// /// Gets all. /// /// The all. diff --git a/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs b/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs index e0f15fa2..eec6199b 100644 --- a/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs +++ b/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs @@ -85,6 +85,12 @@ public override async Task>> BaseGetAllAsync> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException(); + } + public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); diff --git a/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs b/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs index ff6ed533..410a0a35 100644 --- a/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs +++ b/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs @@ -336,6 +336,11 @@ public override IDictionary> BaseGetAll(IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + throw new NotSupportedException(); + } + public override IDictionary> BaseGetByPrefix(string prefix) { ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); diff --git a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs index 80393de0..c3b04e37 100644 --- a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs +++ b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.Async.cs @@ -29,6 +29,10 @@ public override async Task BaseFlushAsync(CancellationToken cancellationToken = } } + public override Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException(); + } public override async Task>> BaseGetAllAsync(IEnumerable cacheKeys, CancellationToken cancellationToken = default) diff --git a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs index f2e2e840..5a2643dd 100644 --- a/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs +++ b/src/EasyCaching.FasterKv/DefaultFasterKvCachingProvider.cs @@ -161,6 +161,11 @@ public override CacheValue BaseGet(string cacheKey) using var session = GetSession(); return BaseGetInternal(cacheKey, session); } + + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + throw new NotSupportedException(); + } public override IDictionary> BaseGetAll(IEnumerable cacheKeys) { diff --git a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs index 45ea96ab..3c2d696d 100644 --- a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs +++ b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs @@ -275,6 +275,21 @@ public override async Task>> BaseGetAllAsync(cacheKeys)); } + + + /// + /// Get all cacheKey by prefix async. + /// + /// Cache keys. + /// Cache keys. + /// Get all cacheKey by prefix async. + public override Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + if (_options.EnableLogging) + _logger?.LogInformation("GetAllKeysAsync"); + + return Task.FromResult(_cache.GetAllKeys(prefix)); + } /// /// Gets the by prefix async. diff --git a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs index e1faa2fc..5eb78bc6 100644 --- a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs +++ b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs @@ -307,6 +307,19 @@ public override IDictionary> BaseGetAll(IEnumerable(cacheKeys); } + /// + /// Get all cacheKey by prefix. + /// + /// Prefix. + /// Get all cacheKey by prefix. + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + if (_options.EnableLogging) + _logger?.LogInformation("GetAllKeys"); + + return _cache.GetAllKeys(prefix); + } + /// /// Gets the by prefix. /// diff --git a/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs b/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs index 944b6dbd..cb23b24d 100644 --- a/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs +++ b/src/EasyCaching.InMemory/Internal/IInMemoryCaching.cs @@ -19,6 +19,7 @@ public interface IInMemoryCaching bool Remove(string key); int RemoveByPrefix(string prefix); int RemoveByPattern(string searchKey, SearchKeyPattern searchPattern); + IEnumerable GetAllKeys(string prefix); IDictionary> GetAll(IEnumerable keys); int SetAll(IDictionary values, TimeSpan? expiresIn = null); bool Replace(string key, T value, TimeSpan? expiresIn = null); diff --git a/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs b/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs index c93e6366..408f7fa8 100644 --- a/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs +++ b/src/EasyCaching.InMemory/Internal/InMemoryCaching.cs @@ -280,6 +280,12 @@ public int RemoveByPattern(string searchKey, SearchKeyPattern searchPattern) return RemoveAll(keysToRemove); } + public IEnumerable GetAllKeys(string prefix) + { + return _memory.Values.Where(x => x.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && x.ExpiresAt > SystemClock.UtcNow) + .Select(x=> x.Key).ToList(); + } + private static bool FilterByPattern(string key, string searchKey, SearchKeyPattern searchKeyPattern) { switch (searchKeyPattern) @@ -306,6 +312,15 @@ public IDictionary> GetAll(IEnumerable keys) return map; } + public IDictionary> GetAll(string prefix = "") + { + var values = string.IsNullOrEmpty(prefix) + ? _memory.Values.Where(x => x.ExpiresAt > SystemClock.UtcNow) + : _memory.Values.Where(x => x.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && x.ExpiresAt > SystemClock.UtcNow); + + return values.ToDictionary(k => k.Key, v => new CacheValue(v.GetValue(_options.EnableReadDeepClone), true)); + } + public int SetAll(IDictionary values, TimeSpan? expiresIn = null) { if (values == null || values.Count == 0) return 0; diff --git a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs index 85e13da0..466c95fb 100644 --- a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs +++ b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs @@ -169,6 +169,11 @@ public override async Task>> BaseGetAllAsync BaseGetAll(cacheKeys), cancellationToken); } + + public override Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException(); + } /// /// Gets the by prefix async. diff --git a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs index 7514987f..908919a6 100644 --- a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs +++ b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs @@ -321,6 +321,11 @@ public override IDictionary> BaseGetAll(IEnumerable(list); } + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + throw new NotSupportedException(); + } + /// /// Gets the dict. /// diff --git a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs index a3287de7..1d96f1d5 100644 --- a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs +++ b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs @@ -262,6 +262,11 @@ public override async Task>> BaseGetAllAsync pair.Key, pair => ConvertFromStoredValue(pair.Value)); } + + public override Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException(); + } /// /// Gets the by prefix async. diff --git a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs index 107bef3f..f0e15a58 100644 --- a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs +++ b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs @@ -336,6 +336,11 @@ public override IDictionary> BaseGetAll(IEnumerable ConvertFromStoredValue(pair.Value)); } + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + throw new NotSupportedException(); + } + /// /// Gets the by prefix. /// diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs index 8e967c75..3604b5df 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs @@ -295,6 +295,27 @@ public override async Task>> BaseGetAllAsync + /// Gets all keys async by prefix. + /// + /// Prefix + /// CancellationToken + /// The all keys by prefix async. + public override async Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); + + prefix = this.HandlePrefix(prefix); + + var redisKeys = this.GetAllRedisKeys(prefix); + + var result = redisKeys?.Select(key => (string) key)?.Distinct(); + + return await Task.FromResult(result); + } + /// /// Gets the by prefix async. diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs index c6e4f61d..fc81d510 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs @@ -351,6 +351,33 @@ private RedisKey[] SearchRedisKeys(string pattern) //return keys.ToArray(); } + + + /// + /// GetAll the redis keys. + /// + /// The redis keys. + /// + /// If your Redis Servers support command SCAN , + /// IServer.Keys will use command SCAN to find out the keys. + /// Following + /// https://github.com/StackExchange/StackExchange.Redis/blob/master/StackExchange.Redis/StackExchange/Redis/RedisServer.cs#L289 + /// + /// Pattern. + private RedisKey[] GetAllRedisKeys(string pattern) + { + var keys = new List(); + + foreach (var server in _servers) + keys.AddRange(server.Keys(pattern: pattern, database: _cache.Database)); + + if (!string.IsNullOrWhiteSpace(_options.DBConfig.KeyPrefix)) + keys = keys.Select(x => new RedisKey( + x.ToString().Remove(0, _options.DBConfig.KeyPrefix.Length))) + .ToList(); + + return keys.Distinct().ToArray(); + } /// /// Handles the prefix of CacheKey. @@ -437,6 +464,24 @@ public override IDictionary> BaseGetAll(IEnumerable + /// Gets all keys by prefix. + /// + /// Prefix + /// The all keys by prefix. + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); + + prefix = this.HandlePrefix(prefix); + + var redisKeys = this.GetAllRedisKeys(prefix); + + var result = redisKeys?.Select(key => (string) key)?.Distinct(); + + return result; + } + /// /// Gets the by prefix. /// diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs index c4973ceb..5463aec2 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs @@ -313,6 +313,11 @@ public override async Task>> BaseGetAllAsync(list); } + + public override Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + throw new NotSupportedException(); + } /// /// Gets the by prefix async. diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs index e9e6aafa..95a76d68 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs @@ -297,6 +297,11 @@ public override IDictionary> BaseGetAll(IEnumerable(list); } + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + throw new NotSupportedException(); + } + /// /// Gets the dict. /// diff --git a/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs b/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs index a64ebad7..81257398 100644 --- a/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Diagnostics/MyCachingProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using System.Linq; public class MyCachingProvider : EasyCachingAbstractProvider { @@ -47,6 +48,16 @@ public override CacheValue BaseGet(string cacheKey) return CacheValue.NoValue; } + public override IEnumerable BaseGetAllKeysByPrefix(string prefix) + { + return Enumerable.Empty(); + } + + public override Task> BaseGetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + return Task.FromResult(Enumerable.Empty()); + } + public override IDictionary> BaseGetAll(IEnumerable cacheKeys) { return null; diff --git a/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs b/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs index a059593f..67b750dc 100644 --- a/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Fake/FakeDistributedCachingProvider.cs @@ -5,7 +5,8 @@ using System.Threading; using System.Threading.Tasks; using EasyCaching.Core; - + using System.Linq; + public class FakeDistributedCachingProvider : IEasyCachingProvider { public string Name => "distributed"; @@ -53,6 +54,16 @@ public virtual CacheValue Get(string cacheKey) { return new CacheValue(default(T), true); } + + public IEnumerable GetAllKeysByPrefix(string prefix) + { + return Enumerable.Empty(); + } + + public Task> GetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + return Task.FromResult(Enumerable.Empty()); + } public virtual IDictionary> GetAll(IEnumerable cacheKeys) { diff --git a/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs b/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs index ebc6275b..9638084f 100644 --- a/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs +++ b/test/EasyCaching.UnitTests/Fake/FakeLocalCachingProvider.cs @@ -5,7 +5,8 @@ using System.Threading; using System.Threading.Tasks; using EasyCaching.Core; - + using System.Linq; + public class FakeLocalCachingProvider : IEasyCachingProvider { public string Name => "local"; @@ -53,6 +54,16 @@ public CacheValue Get(string cacheKey) { return new CacheValue(default(T), true); } + + public IEnumerable GetAllKeysByPrefix(string prefix) + { + return Enumerable.Empty(); + } + + public Task> GetAllKeysByPrefixAsync(string prefix, CancellationToken cancellationToken = default) + { + return Task.FromResult(Enumerable.Empty()); + } public IDictionary> GetAll(IEnumerable cacheKeys) { From e32f7113a70cd9b35eab88f8c9cd574a511a3aa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 08:44:33 +0800 Subject: [PATCH 08/12] chore(deps): bump Newtonsoft.Json in /src/EasyCaching.SQLite (#431) Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.3 to 13.0.2. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.3...13.0.2) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/EasyCaching.SQLite/EasyCaching.SQLite.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EasyCaching.SQLite/EasyCaching.SQLite.csproj b/src/EasyCaching.SQLite/EasyCaching.SQLite.csproj index e789a836..748bf990 100644 --- a/src/EasyCaching.SQLite/EasyCaching.SQLite.csproj +++ b/src/EasyCaching.SQLite/EasyCaching.SQLite.csproj @@ -36,7 +36,7 @@ - + From 62fe591b94b63c9bf55409777275afbb62fc350d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 09:59:51 +0800 Subject: [PATCH 09/12] chore(deps): bump Newtonsoft.Json (#432) Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.3 to 13.0.2. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.3...13.0.2) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../EasyCaching.Serialization.Json.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serialization/EasyCaching.Serialization.Json/EasyCaching.Serialization.Json.csproj b/serialization/EasyCaching.Serialization.Json/EasyCaching.Serialization.Json.csproj index 8d84a143..160993d8 100644 --- a/serialization/EasyCaching.Serialization.Json/EasyCaching.Serialization.Json.csproj +++ b/serialization/EasyCaching.Serialization.Json/EasyCaching.Serialization.Json.csproj @@ -32,7 +32,7 @@ - + From 4cc0ab99a90c86342c0d24aca77ba13d1ee805f5 Mon Sep 17 00:00:00 2001 From: Amir Solhi Date: Mon, 19 Dec 2022 04:03:59 +0330 Subject: [PATCH 10/12] zremrangebyscore support for both Redis and CSRedis (#436) Co-authored-by: amirsolhi --- ...DefaultCSRedisCachingProvider.SortedSet.cs | 17 ++++++++- src/EasyCaching.Core/IRedisCachingProvider.cs | 18 +++++++++ .../DefaultRedisCachingProvider.SortedSet.cs | 15 +++++++- .../BaseRedisFeatureCachingProviderTest.cs | 37 +++++++++++++++++-- 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs index 9465fc3e..08ee7076 100755 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs @@ -1,7 +1,6 @@ namespace EasyCaching.CSRedis { using EasyCaching.Core; - using EasyCaching.Core.Internal; using System.Collections.Generic; using System.Threading.Tasks; @@ -86,6 +85,13 @@ public List ZRangeByScore(string cacheKey, double min, double max, long? c return list; } + public long ZRangeRemByScore(string cacheKey, double min, double max) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + return _cache.ZRemRangeByScore(cacheKey, (decimal)min, (decimal)max); + } + public long? ZRank(string cacheKey, T cacheValue) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); @@ -163,7 +169,7 @@ public async Task ZIncrByAsync(string cacheKey, string field, double val ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullOrWhiteSpace(field, nameof(field)); - var value= await _cache.ZIncrByAsync(cacheKey, field, (decimal)val); + var value = await _cache.ZIncrByAsync(cacheKey, field, (decimal)val); return (double)value; } @@ -207,6 +213,13 @@ public async Task> ZRangeByScoreAsync(string cacheKey, double min, do return list; } + public async Task ZRangeRemByScoreAsync(string cacheKey, double min, double max) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + return await _cache.ZRemRangeByScoreAsync(cacheKey, (decimal)min, (decimal)max); + } + public async Task ZRankAsync(string cacheKey, T cacheValue) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); diff --git a/src/EasyCaching.Core/IRedisCachingProvider.cs b/src/EasyCaching.Core/IRedisCachingProvider.cs index a317deeb..fc5b6beb 100644 --- a/src/EasyCaching.Core/IRedisCachingProvider.cs +++ b/src/EasyCaching.Core/IRedisCachingProvider.cs @@ -752,6 +752,16 @@ public interface IRedisCachingProvider /// /// List ZRangeByScore(string cacheKey, double min, double max, long? count = null, long offset = 0); + + /// + /// https://redis.io/commands/zremrangebyscore/ + /// + /// + /// + /// + /// The number of elements removed + long ZRangeRemByScore(string cacheKey, double min, double max); + /// /// https://redis.io/commands/zrank /// @@ -835,6 +845,14 @@ public interface IRedisCachingProvider /// Task> ZRangeByScoreAsync(string cacheKey, double min, double max, long? count = null, long offset = 0); /// + /// https://redis.io/commands/zremrangebyscore/ + /// + /// + /// + /// + /// The number of elements removed + Task ZRangeRemByScoreAsync(string cacheKey, double min, double max); + /// /// https://redis.io/commands/zrank /// /// diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.SortedSet.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.SortedSet.cs index 4196330f..84e99873 100755 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.SortedSet.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.SortedSet.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using EasyCaching.Core; - using EasyCaching.Core.Internal; using StackExchange.Redis; /// @@ -92,6 +91,13 @@ public List ZRangeByScore(string cacheKey, double min, double max, long? c return list; } + public long ZRangeRemByScore(string cacheKey, double min, double max) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + return _cache.SortedSetRemoveRangeByScore(cacheKey, min, max); + } + public long? ZRank(string cacheKey, T cacheValue) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); @@ -213,6 +219,13 @@ public async Task> ZRangeByScoreAsync(string cacheKey, double min, do return list; } + public async Task ZRangeRemByScoreAsync(string cacheKey, double min, double max) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + return await _cache.SortedSetRemoveRangeByScoreAsync(cacheKey, min, max); + } + public async Task ZRankAsync(string cacheKey, T cacheValue) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); diff --git a/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs index d2da866e..82bf69f4 100644 --- a/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using Xunit; + + public abstract class BaseRedisFeatureCachingProviderTest { protected IRedisCachingProvider _provider; @@ -81,7 +83,7 @@ protected virtual async Task StringSet_And_KeyExpire_And_TTL_Async_Should_Succee await _provider.KeyDelAsync(cacheKey); } - + [Fact] protected virtual async Task StringSetWithExpiration_And_Persist_And_TTL_Async_Should_Succeed() { @@ -90,7 +92,7 @@ protected virtual async Task StringSetWithExpiration_And_Persist_And_TTL_Async_S var res = await _provider.StringSetAsync(cacheKey, "123", TimeSpan.FromSeconds(10)); Assert.True(res); - + var initialTtl = await _provider.TTLAsync(cacheKey); Assert.InRange(initialTtl, 1, 10); @@ -1689,7 +1691,36 @@ protected virtual async void ZRangeByScoreAsync_Should_Succeed() Assert.Contains("two", items); _baseProvider.Remove(cacheKey); - } + } + + [Fact] + protected virtual async void ZRangeRemByScoreAsync_Should_Succeed() + { + var cacheKey = $"{_nameSpace}-{Guid.NewGuid().ToString()}"; + + var dict = new Dictionary() + { + { "one", 1}, + { "two", 2}, + { "three", 3}, + { "four", 4}, + }; + + var res = await _provider.ZAddAsync(cacheKey, dict); + + var deletedCount = await _provider.ZRangeRemByScoreAsync(cacheKey, 1, 2); + + var items = await _provider.ZRangeByScoreAsync(cacheKey, 1, 4); + + Assert.Equal(2, deletedCount); + Assert.DoesNotContain("one", items); + Assert.DoesNotContain("two", items); + Assert.Contains("three", items); + Assert.Contains("four", items); + + _baseProvider.Remove(cacheKey); + } + #endregion #region Hyperloglog From db8c5af77e34bf1d1c686fdc397b0b9f5b232f75 Mon Sep 17 00:00:00 2001 From: Amir Solhi Date: Sat, 31 Dec 2022 17:07:59 +0330 Subject: [PATCH 11/12] MemoryPack Serializer (#438) * MemoryPack serializer * some cleanups Co-authored-by: amirsolhi --- EasyCaching.sln | 9 +- .../EasyCaching.Demo.ConsoleApp.csproj | 7 +- sample/EasyCaching.Demo.ConsoleApp/Program.cs | 76 ++++++++++----- .../EasyCachingMemPackSerializerOptions.cs | 13 +++ .../EasyCachingOptionsExtensions.cs | 36 +++++++ .../MemoryPackOptionsExtension.cs | 55 +++++++++++ .../DefaultMemoryPackSerializer.cs | 33 +++++++ ...asyCaching.Serialization.MemoryPack.csproj | 13 +++ .../CachingTests/BaseCachingProviderTest.cs | 10 +- .../EasyCaching.UnitTests.csproj | 28 ++++-- .../SerializerTests/BaseSerializerTest.cs | 6 +- .../MemoryPackSerializerTest.cs | 96 +++++++++++++++++++ 12 files changed, 338 insertions(+), 44 deletions(-) create mode 100644 serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs create mode 100644 serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs create mode 100644 serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs create mode 100644 serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs create mode 100644 serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj create mode 100644 test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs diff --git a/EasyCaching.sln b/EasyCaching.sln index 3f2504a3..7d07ad0a 100644 --- a/EasyCaching.sln +++ b/EasyCaching.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32616.157 MinimumVisualStudioVersion = 10.0.40219.1 @@ -76,6 +76,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Bus.Zookeeper", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.FasterKv", "src\EasyCaching.FasterKv\EasyCaching.FasterKv.csproj", "{7191E567-38DF-4879-82E1-73EC618AFCAC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Serialization.MemoryPack", "serialization\EasyCaching.Serialization.MemoryPack\EasyCaching.Serialization.MemoryPack.csproj", "{EEF22C21-F380-4980-B72C-F14488369333}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,6 +204,10 @@ Global {7191E567-38DF-4879-82E1-73EC618AFCAC}.Debug|Any CPU.Build.0 = Debug|Any CPU {7191E567-38DF-4879-82E1-73EC618AFCAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {7191E567-38DF-4879-82E1-73EC618AFCAC}.Release|Any CPU.Build.0 = Release|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -237,6 +243,7 @@ Global {F7FBADEB-D766-4595-949A-07104B52692C} = {B337509B-75F9-4851-821F-9BBE87C4E4BC} {5E488583-391E-4E15-83C1-7301B4FE79AE} = {B337509B-75F9-4851-821F-9BBE87C4E4BC} {7191E567-38DF-4879-82E1-73EC618AFCAC} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + {EEF22C21-F380-4980-B72C-F14488369333} = {15070C49-A507-4844-BCFE-D319CFBC9A63} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63A57886-054B-476C-AAE1-8D7C8917682E} diff --git a/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj b/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj index 91b6c494..10bc10bd 100644 --- a/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj +++ b/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj @@ -20,10 +20,13 @@ + + + + - + - diff --git a/sample/EasyCaching.Demo.ConsoleApp/Program.cs b/sample/EasyCaching.Demo.ConsoleApp/Program.cs index 03f7b282..3885d5ee 100644 --- a/sample/EasyCaching.Demo.ConsoleApp/Program.cs +++ b/sample/EasyCaching.Demo.ConsoleApp/Program.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using EasyCaching.Serialization.MemoryPack; namespace EasyCaching.Demo.ConsoleApp { using EasyCaching.Core; using EasyCaching.SQLite; + using MemoryPack; using Microsoft.Extensions.DependencyInjection; using System; @@ -16,15 +17,18 @@ static void Main(string[] args) IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(option => { + option.WithMemoryPack(configure => + { + }, "mempack"); + option.UseInMemory("m1"); - // option.UseRedis(config => - // { - // config.DBConfig = new Redis.RedisDBOptions { Configuration = "localhost" }; - // config.SerializerName = "json"; - // }, "r1"); - // - + option.UseRedis((options) => + { + options.SerializerName = "mempack"; + options.DBConfig.Endpoints.Add(new Core.Configurations.ServerEndPoint("localhost", 6388)); + }, "r1"); + option.UseSQLite(c => { c.DBConfig = new SQLiteDBOptions @@ -43,7 +47,7 @@ static void Main(string[] args) IServiceProvider serviceProvider = services.BuildServiceProvider(); var factory = serviceProvider.GetService(); - + // var redisCache = factory.GetCachingProvider("r1"); // // redisCache.Set("rkey", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); @@ -55,41 +59,63 @@ static void Main(string[] args) // Console.WriteLine($"redis cache get value, {redisVal.HasValue} {redisVal.IsNull} {redisVal.Value}"); - - var mCache = factory.GetCachingProvider("m1"); - - mCache.Set("mkey1", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); - var mVal1 = mCache.Get("mkey1"); + var rCache = factory.GetCachingProvider("r1"); + + var prod = new Product() + { + Name = "Name1", + Lastname = "Lastname1", + Inner = new() + { + Name = "Name2", + Lastname = "Lastname2" + } + }; + + prod.Inner.Inner = prod; + rCache.Set("mkey1", prod, TimeSpan.FromSeconds(20)); + + var mVal1 = rCache.Get("mkey1"); + + rCache.Set("mkey", "mvalue", TimeSpan.FromSeconds(20)); + + var mVal = rCache.Get("mkey"); + + var mAllKey = rCache.GetAllKeysByPrefix("mk"); - mCache.Set("mkey", "mvalue", TimeSpan.FromSeconds(20)); - - var mVal = mCache.Get("mkey"); - - var mAllKey = mCache.GetAllKeysByPrefix("mk"); - Console.WriteLine($"in-memory cache get value, {mVal.HasValue} {mVal.IsNull} {mVal.Value} "); - + var sCache = factory.GetCachingProvider("s1"); - + sCache.Set("skey", "svalue", TimeSpan.FromSeconds(20)); - + var sVal = sCache.Get("skey"); - + Console.WriteLine($"sqlite cache get value, {sVal.HasValue} {sVal.IsNull} {sVal.Value} "); Console.ReadKey(); } } - public class Product + [MemoryPackable(GenerateType.CircularReference)] + public partial class Product { + [MemoryPackOrder(0)] public string Name { get; set; } + + [MemoryPackOrder(1)] + + public string Lastname { get; set; } + + [MemoryPackOrder(2)] + + public Product Inner { set; get; } } } diff --git a/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs new file mode 100644 index 00000000..033aa04d --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs @@ -0,0 +1,13 @@ +using MemoryPack; + +namespace EasyCaching.Serialization.MemoryPack; + +/// +/// EasyCaching memory pack serializer options. +/// +public record EasyCachingMemPackSerializerOptions +{ + public StringEncoding StringEncoding { set; get; } +} + + diff --git a/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs new file mode 100644 index 00000000..6764f372 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs @@ -0,0 +1,36 @@ +using MemoryPack; +using EasyCaching.Core.Configurations; +using EasyCaching.Serialization.Json; + +namespace EasyCaching.Serialization.MemoryPack; + +/// +/// Easy caching options extensions. +/// +public static class EasyCachingOptionsExtensions +{ + /// + /// Withs the memory pack serializer. + /// + /// Options. + /// The name of this serializer instance. + public static EasyCachingOptions WithMemoryPack(this EasyCachingOptions options, string name = "mempack") + { + options.RegisterExtension(new MemoryPackOptionsExtension(name, null)); + + return options; + } + + /// + /// Withs the memory pack serializer. + /// + /// Options. + /// Configure serializer settings. + /// The name of this serializer instance. + public static EasyCachingOptions WithMemoryPack(this EasyCachingOptions options, Action serializerOptions, string name) + { + options.RegisterExtension(new MemoryPackOptionsExtension(name, serializerOptions)); + + return options; + } +} diff --git a/serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs new file mode 100644 index 00000000..58126711 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs @@ -0,0 +1,55 @@ +namespace EasyCaching.Serialization.Json; + +using System; +using EasyCaching.Core.Configurations; +using EasyCaching.Core.Serialization; +using EasyCaching.Serialization.MemoryPack; +using global::MemoryPack; +using Microsoft.Extensions.DependencyInjection; + +/// +/// MemoryPack options extension. +/// +internal sealed class MemoryPackOptionsExtension : IEasyCachingOptionsExtension +{ + /// + /// The name. + /// + private readonly string _name; + + /// + /// The configure. + /// + private readonly Action _configure; + + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// Configure. + public MemoryPackOptionsExtension(string name, Action configure) + { + this._name = name; + this._configure = configure; + } + + /// + /// Adds the services. + /// + /// Services. + public void AddServices(IServiceCollection services) + { + Action configure = _configure ?? (_ => { }); + + services.AddOptions(); + services.Configure(_name, configure); + + services.AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var easyCachingOptions = optionsMon.Get(_name); + var options = new MemoryPackSerializerOptions { StringEncoding = easyCachingOptions.StringEncoding }; + return new DefaultMemoryPackSerializer(_name, options); + }); + } +} diff --git a/serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs b/serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs new file mode 100644 index 00000000..e8d01528 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs @@ -0,0 +1,33 @@ +using EasyCaching.Core.Serialization; +using MemoryPack; + +namespace EasyCaching.Serialization.MemoryPack; + +/// +/// Default MemoryPack serializer +/// +public class DefaultMemoryPackSerializer : IEasyCachingSerializer +{ + private readonly string _name; + private readonly MemoryPackSerializerOptions _memoryPackSerializerOptions; + + public string Name => _name; + + public DefaultMemoryPackSerializer(string name, MemoryPackSerializerOptions options = null) + { + _name = name; + _memoryPackSerializerOptions = options; + } + + public T Deserialize(byte[] bytes) => MemoryPackSerializer.Deserialize(bytes, _memoryPackSerializerOptions); + public object Deserialize(byte[] bytes, Type type) => MemoryPackSerializer.Deserialize(type, bytes, _memoryPackSerializerOptions); + public object DeserializeObject(ArraySegment value) => throw new NotImplementedException("this is not supported in MemoryPack serializer"); + public byte[] Serialize(T value) => MemoryPackSerializer.Serialize(value, _memoryPackSerializerOptions); + + public ArraySegment SerializeObject(object obj) + { + var bytes = MemoryPackSerializer.Serialize(obj.GetType(), obj, _memoryPackSerializerOptions); + return new ArraySegment(bytes); + } +} + diff --git a/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj b/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj new file mode 100644 index 00000000..91d53fd7 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + + + + + + + + diff --git a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs index 9b359f10..0e70a5ba 100644 --- a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs @@ -765,7 +765,7 @@ public async Task Remove_Cached_Value_Async_Should_Succeed() var cacheValue = "value"; await _provider.SetAsync(cacheKey, cacheValue, _defaultTs); - var valBeforeRemove = await _provider.GetAsync(cacheKey, null, _defaultTs); + var valBeforeRemove = await _provider.GetAsync(cacheKey, () => null, _defaultTs); Assert.NotNull(valBeforeRemove); await _provider.RemoveAsync(cacheKey); @@ -870,7 +870,7 @@ protected virtual async Task RemoveByPrefixAsync_Should_Succeed() } #endregion - + #region RemoveByPattern/RemoveByPatternAsync [Fact] @@ -878,7 +878,7 @@ public virtual void RemoveByPattern_Should_Succeed() { SetCacheItem("garden:pots:flowers", "ok"); SetCacheItem("garden:pots:flowers:test", "ok"); - SetCacheItem("garden:flowerspots:test", "ok" ); + SetCacheItem("garden:flowerspots:test", "ok"); SetCacheItem("boo:foo", "ok"); SetCacheItem("boo:test:foo", "ok"); SetCacheItem("sky:birds:bar", "ok"); @@ -933,13 +933,13 @@ public virtual void RemoveByPattern_Should_Succeed() Assert.False(val15.HasValue); Assert.False(val16.HasValue); } - + [Fact] public virtual async Task RemoveByPatternAsync_Should_Succeed() { SetCacheItem("garden:pots:flowers", "ok"); SetCacheItem("garden:pots:flowers:test", "ok"); - SetCacheItem("garden:flowerspots:test", "ok" ); + SetCacheItem("garden:flowerspots:test", "ok"); SetCacheItem("boo:foo", "ok"); SetCacheItem("boo:test:foo", "ok"); SetCacheItem("sky:birds:bar", "ok"); diff --git a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj index 26b51b1d..4c5c5b3b 100644 --- a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj +++ b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj @@ -30,15 +30,6 @@ - - - - - - - - - @@ -51,4 +42,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs b/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs index 49dbcda4..bfc5a26d 100644 --- a/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs +++ b/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs @@ -2,6 +2,7 @@ { using System; using EasyCaching.Core.Serialization; + using MemoryPack; using ProtoBuf; using Xunit; @@ -38,7 +39,7 @@ public void SerializeObject_should_Succeed() } [Fact] - public void DeserializeObject_should_Succeed() + public virtual void DeserializeObject_should_Succeed() { object obj = new Model { Prop = "abc" }; @@ -76,7 +77,8 @@ public void Deserialize_String_Should_Succeed(string str) [Serializable] [ProtoContract] - public class Model + [MemoryPackable] + public partial class Model { [ProtoMember(1)] public string Prop { get; set; } diff --git a/test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs b/test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs new file mode 100644 index 00000000..32cbbfb9 --- /dev/null +++ b/test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs @@ -0,0 +1,96 @@ +using System; +using EasyCaching.Serialization.MemoryPack; +using MemoryPack; +using Xunit; + +namespace EasyCaching.UnitTests; + + +public class MemoryPackSerializerTest : BaseSerializerTest +{ + public MemoryPackSerializerTest() + { + _serializer = new DefaultMemoryPackSerializer("mempack"); + } + + //This should be overrided becuse it is not supported by memory-pack + public override void DeserializeObject_should_Succeed() + { + Person input = new("test", "test1"); + var serialized = _serializer.Serialize(input); + + Assert.Throws(() => + { + _serializer.DeserializeObject(new System.ArraySegment(serialized)); + }); + } + + [Fact] + public void GivenSampleRecord_ShouldSerializeAndDeserializeSuccessfuly() + { + Person input = new("test", "test1"); + var bytes = _serializer.Serialize(input); + + Person output = _serializer.Deserialize(bytes); + + Assert.Equal(input, output); + } + + [Fact] + public void GivenSampleRecord_ShouldHandleNestedObjectSuccessfuly() + { + NestedPerson item1 = new() { Name = "test", Lastname = "test1" }; + NestedPerson expected = new() { Name = "test2", Lastname = "test3", Inner = item1 }; + + var bytes = _serializer.Serialize(expected); + + NestedPerson output = _serializer.Deserialize(bytes); + + Assert.Equal(expected, output); + } + + [Fact] + public void GivenSampleInput_ShouldHandleCircularRefSuccessfuly() + { + CircularPerson person = new CircularPerson() + { + Name = "test" + }; + + person.Self = person; + + var bytes = _serializer.Serialize(person); + var output = _serializer.Deserialize(bytes); + + bool expected = + person.Name == output.Name && + output.Self == output && + output.Name == output.Self.Name; + + Assert.True(expected); + } +} + +#region Test Models +[MemoryPackable(GenerateType.CircularReference)] +internal partial class CircularPerson +{ + [MemoryPackOrder(0)] + public string Name { set; get; } + [MemoryPackOrder(1)] + public CircularPerson Self { set; get; } +} + +[MemoryPackable] +internal partial record struct Person(string Name, string Lastname); + +[MemoryPackable] +internal partial record class NestedPerson +{ + public string Name { set; get; } + + public string Lastname { set; get; } + + public NestedPerson Inner { set; get; } +} +#endregion From 9319b1bd7b6a381fda9ebabb29add3ff8034f8d0 Mon Sep 17 00:00:00 2001 From: catcherwong Date: Sun, 1 Jan 2023 10:43:40 +0800 Subject: [PATCH 12/12] update version to 1.8.0 --- build/releasenotes.props | 152 +++++++++--------- build/version.props | 46 +++--- ...asyCaching.Serialization.MemoryPack.csproj | 55 +++++-- .../EasyCaching.FasterKv.csproj | 35 +++- 4 files changed, 174 insertions(+), 114 deletions(-) diff --git a/build/releasenotes.props b/build/releasenotes.props index f2f98711..4ab7c231 100644 --- a/build/releasenotes.props +++ b/build/releasenotes.props @@ -1,76 +1,80 @@ - - - 1. Remove BinaryFormatter. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - - - 1. Upgrading dependencies. - - - 1. Add EasyCachingKafkaBus. - - - 1. Add EasyCachingZookeeperBus. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - 2. Support removing cache keys by pattern. - - - 1. Upgrading dependencies. - - + + + 1. Add GetAllKeysByPrefix. + 2. Support zremrangebyscore + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + 2. Support zremrangebyscore + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + 2. Support evicted event hook. + + + 1. Upgrading dependencies. + 2. Added a HybridCachingOption to throw an error if the distributed cache throws an error. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + 2. Support zremrangebyscore + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + 2. Use DefaultObjectPoolProvider to get a DisposableObjectPool instead of a DefaultObjectPool + 3. Restore queue model and consumer on disconnection + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Upgrading dependencies. + + + 1. Support Memory Pack. + + + 1. Support FaskKV. + + diff --git a/build/version.props b/build/version.props index dee60423..ab915bf5 100644 --- a/build/version.props +++ b/build/version.props @@ -1,26 +1,28 @@ - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 - 1.7.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 + 1.8.0 diff --git a/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj b/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj index 91d53fd7..5888b767 100644 --- a/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj +++ b/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj @@ -1,13 +1,44 @@ - - - - net6.0 - enable - - - - - - - + + + + + + net6.0 + ncc;Amir Solhi + ncc;Amir Solhi + enable + $(EasyCachingMemoryPackageVersion) + + + A serialize library based on MemoryPack + + Caching,Serialization,MemoryPack + https://github.com/dotnetcore/EasyCaching + LICENSE + https://github.com/dotnetcore/EasyCaching + https://github.com/dotnetcore/EasyCaching + nuget-icon.png + + $(EasyCachingMemoryPackageNotes) + + + + + true + $(NoWarn);1591 + + + + + + + + + + + + + + + diff --git a/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj b/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj index 99c28038..a232270b 100644 --- a/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj +++ b/src/EasyCaching.FasterKv/EasyCaching.FasterKv.csproj @@ -1,16 +1,39 @@ - - netstandard2.0;net6.0 - 10 - enable - true - + + + + netstandard2.0;net6.0 + ncc;InCerry + ncc;InCerry + 10 + enable + true + $(EasyCachingFaskKVPackageVersion) + + + A simple local caching provider based on FASTER. + + FASTER,File,LocalCache,Caching,Cache + https://github.com/dotnetcore/EasyCaching + LICENSE + https://github.com/dotnetcore/EasyCaching + https://github.com/dotnetcore/EasyCaching + nuget-icon.png + + $(EasyCachingFaskKVPackageNotes) + + + + + + +