From a5382cff4ae1cc4e753fd1c2a6c28607cf69f9a3 Mon Sep 17 00:00:00 2001 From: Michael Grundberg Date: Tue, 1 Oct 2019 16:04:42 +0200 Subject: [PATCH 1/4] Added a RemoveAll function to clear the entire cache --- .../CachingServiceMemoryCacheProviderTests.cs | 15 ++++++++++++++- LazyCache/CachingService.cs | 7 ++++++- LazyCache/IAppCache.cs | 2 ++ LazyCache/ICacheProvider.cs | 1 + LazyCache/Mocks/MockCacheProvider.cs | 4 ++++ LazyCache/Mocks/MockCachingService.cs | 4 ++++ LazyCache/Providers/MemoryCacheProvider.cs | 13 ++++++++++--- 7 files changed, 41 insertions(+), 5 deletions(-) diff --git a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs index 85e5e8b..d3e724f 100644 --- a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs +++ b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs @@ -21,7 +21,7 @@ public void BeforeEachTest() private static CachingService BuildCache() { - return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); + return new CachingService(new MemoryCacheProvider(() => new MemoryCache(new MemoryCacheOptions()))); } private IAppCache sut; @@ -755,5 +755,18 @@ public void RemovedItemCannotBeRetrievedFromCache() sut.Remove(TestKey); Assert.Null(sut.Get(TestKey)); } + + [Test] + public void NoItemsCanBeRetrievedFromCacheAfterRemoveAll() + { + var testKey2 = "TestKey2"; + sut.Add(TestKey, new object()); + sut.Add(testKey2, new object()); + Assert.NotNull(sut.Get(TestKey)); + Assert.NotNull(sut.Get(testKey2)); + sut.RemoveAll(); + Assert.Null(sut.Get(TestKey)); + Assert.Null(sut.Get(testKey2)); + } } } \ No newline at end of file diff --git a/LazyCache/CachingService.cs b/LazyCache/CachingService.cs index 1bd5305..824fcff 100644 --- a/LazyCache/CachingService.cs +++ b/LazyCache/CachingService.cs @@ -36,7 +36,7 @@ public CachingService(ICacheProvider cache) : this(() => cache) public static Lazy DefaultCacheProvider { get; set; } = new Lazy(() => - new MemoryCacheProvider( + new MemoryCacheProvider(() => new MemoryCache( new MemoryCacheOptions()) )); @@ -122,6 +122,11 @@ public virtual void Remove(string key) CacheProvider.Remove(key); } + public virtual void RemoveAll() + { + CacheProvider.RemoveAll(); + } + public virtual ICacheProvider CacheProvider => cacheProvider.Value; public virtual async Task GetOrAddAsync(string key, Func> addItemFactory) diff --git a/LazyCache/IAppCache.cs b/LazyCache/IAppCache.cs index b474cbe..e133f6b 100644 --- a/LazyCache/IAppCache.cs +++ b/LazyCache/IAppCache.cs @@ -24,5 +24,7 @@ public interface IAppCache Task GetOrAddAsync(string key, Func> addItemFactory); void Remove(string key); + + void RemoveAll(); } } \ No newline at end of file diff --git a/LazyCache/ICacheProvider.cs b/LazyCache/ICacheProvider.cs index 2007416..b065c0e 100644 --- a/LazyCache/ICacheProvider.cs +++ b/LazyCache/ICacheProvider.cs @@ -10,6 +10,7 @@ public interface ICacheProvider : IDisposable object Get(string key); object GetOrCreate(string key, Func func); void Remove(string key); + void RemoveAll(); Task GetOrCreateAsync(string key, Func> func); } } \ No newline at end of file diff --git a/LazyCache/Mocks/MockCacheProvider.cs b/LazyCache/Mocks/MockCacheProvider.cs index c7463e7..4ee3ed4 100644 --- a/LazyCache/Mocks/MockCacheProvider.cs +++ b/LazyCache/Mocks/MockCacheProvider.cs @@ -24,6 +24,10 @@ public void Remove(string key) { } + public void RemoveAll() + { + } + public Task GetOrCreateAsync(string key, Func> func) { return func(null); diff --git a/LazyCache/Mocks/MockCachingService.cs b/LazyCache/Mocks/MockCachingService.cs index 51b92fd..a10857f 100644 --- a/LazyCache/Mocks/MockCachingService.cs +++ b/LazyCache/Mocks/MockCachingService.cs @@ -27,6 +27,10 @@ public void Remove(string key) { } + public void RemoveAll() + { + } + public Task GetOrAddAsync(string key, Func> addItemFactory) { return addItemFactory(new MockCacheEntry(key)); diff --git a/LazyCache/Providers/MemoryCacheProvider.cs b/LazyCache/Providers/MemoryCacheProvider.cs index 9bdfdc9..501687e 100644 --- a/LazyCache/Providers/MemoryCacheProvider.cs +++ b/LazyCache/Providers/MemoryCacheProvider.cs @@ -6,11 +6,13 @@ namespace LazyCache.Providers { public class MemoryCacheProvider : ICacheProvider { - internal readonly IMemoryCache cache; + internal readonly Func cacheFactory; + internal IMemoryCache cache; - public MemoryCacheProvider(IMemoryCache cache) + public MemoryCacheProvider(Func cacheFactory) { - this.cache = cache; + this.cacheFactory = cacheFactory; + cache = cacheFactory(); } public void Set(string key, object item, MemoryCacheEntryOptions policy) @@ -33,6 +35,11 @@ public void Remove(string key) cache.Remove(key); } + public void RemoveAll() + { + cache = cacheFactory(); + } + public Task GetOrCreateAsync(string key, Func> factory) { return cache.GetOrCreateAsync(key, factory); From 3e320bc64ac3ed4c718df47fb4280b3ed55c8af6 Mon Sep 17 00:00:00 2001 From: Michael Grundberg Date: Tue, 31 Mar 2020 18:55:41 +0200 Subject: [PATCH 2/4] Dispose is called from the new RemoveAll function, so memory associated with the old cache is released --- LazyCache/Providers/MemoryCacheProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/LazyCache/Providers/MemoryCacheProvider.cs b/LazyCache/Providers/MemoryCacheProvider.cs index 501687e..cab40ca 100644 --- a/LazyCache/Providers/MemoryCacheProvider.cs +++ b/LazyCache/Providers/MemoryCacheProvider.cs @@ -37,6 +37,7 @@ public void Remove(string key) public void RemoveAll() { + cache?.Dispose(); cache = cacheFactory(); } From 000e0514f34dee7392c6184896525fcbe6a1dc90 Mon Sep 17 00:00:00 2001 From: Michael Grundberg Date: Tue, 31 Mar 2020 18:57:48 +0200 Subject: [PATCH 3/4] Fix for injection code as the MemoryCacheProvider constructor has another parameter --- .../LazyCacheServiceCollectionExtensions.cs | 9 +++++---- LazyCache.Ninject/LazyCacheModule.cs | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs b/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs index eb242ff..2129fb9 100644 --- a/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs +++ b/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using LazyCache.Providers; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; // ReSharper disable once CheckNamespace - MS guidelines say put DI registration in this NS namespace Microsoft.Extensions.DependencyInjection @@ -15,8 +16,8 @@ public static IServiceCollection AddLazyCache(this IServiceCollection services) if (services == null) throw new ArgumentNullException(nameof(services)); services.AddOptions(); - services.TryAdd(ServiceDescriptor.Singleton()); - services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton(serviceProvider => + new MemoryCacheProvider(() => new MemoryCache(new MemoryCacheOptions())))); services.TryAdd(ServiceDescriptor.Singleton()); @@ -30,8 +31,8 @@ public static IServiceCollection AddLazyCache(this IServiceCollection services, if (implementationFactory == null) throw new ArgumentNullException(nameof(implementationFactory)); services.AddOptions(); - services.TryAdd(ServiceDescriptor.Singleton()); - services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton(serviceProvider => + new MemoryCacheProvider(() => new MemoryCache(new MemoryCacheOptions())))); services.TryAdd(ServiceDescriptor.Singleton(implementationFactory)); diff --git a/LazyCache.Ninject/LazyCacheModule.cs b/LazyCache.Ninject/LazyCacheModule.cs index c1d7531..d753cd9 100644 --- a/LazyCache.Ninject/LazyCacheModule.cs +++ b/LazyCache.Ninject/LazyCacheModule.cs @@ -2,6 +2,7 @@ using LazyCache.Providers; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; +using Ninject; using Ninject.Modules; namespace LazyCache @@ -23,8 +24,8 @@ public LazyCacheModule(Func implementationFactory) public override void Load() { Bind>().ToConstant(Options.Create(new MemoryCacheOptions())); - Bind().To().InSingletonScope(); - Bind().To().InSingletonScope(); + Bind().To().InSingletonScope() + .WithConstructorArgument>(context => () => new MemoryCache(context.Kernel.Get>())); if (implementationFactory == null) Bind().To().InSingletonScope(); From 806093172fc5c602625194704789daa2e640757a Mon Sep 17 00:00:00 2001 From: Michael Grundberg Date: Tue, 31 Mar 2020 20:52:38 +0200 Subject: [PATCH 4/4] Fixed race condition in RemoveAll() --- LazyCache/Providers/MemoryCacheProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LazyCache/Providers/MemoryCacheProvider.cs b/LazyCache/Providers/MemoryCacheProvider.cs index cab40ca..6c65f71 100644 --- a/LazyCache/Providers/MemoryCacheProvider.cs +++ b/LazyCache/Providers/MemoryCacheProvider.cs @@ -37,8 +37,8 @@ public void Remove(string key) public void RemoveAll() { - cache?.Dispose(); - cache = cacheFactory(); + var oldCache = System.Threading.Interlocked.Exchange(ref cache, cacheFactory()); + oldCache?.Dispose(); } public Task GetOrCreateAsync(string key, Func> factory)