From 03b7a1799003f886519d4ea67cd9d5c59158fedc Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Fri, 7 May 2021 16:54:44 -0700 Subject: [PATCH] add dictionary to maintain cache keys --- Console.Net461/Program.cs | 9 ++ .../CachingServiceMemoryCacheProviderTests.cs | 89 +++++++++++++++++++ LazyCache/CachedItemMeta.cs | 14 +++ LazyCache/CachingService.cs | 35 +++++++- LazyCache/IAppCache.cs | 2 + LazyCache/Mocks/MockCachingService.cs | 7 ++ 6 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 LazyCache/CachedItemMeta.cs diff --git a/Console.Net461/Program.cs b/Console.Net461/Program.cs index 427b77c..c99faa7 100644 --- a/Console.Net461/Program.cs +++ b/Console.Net461/Program.cs @@ -22,6 +22,15 @@ static void Main() item = cache.GetOrAdd("Program.Main.Person", () => Tuple.Create("Joe Blogs", DateTime.UtcNow)); System.Console.WriteLine(item.Item1); + + System.Console.WriteLine("Enumerating keys..."); + foreach (var key in cache.GetCacheKeys().Keys) + { + System.Console.WriteLine($"{key}"); + } + System.Console.WriteLine("Finished enumerating keys..."); + + System.Console.ReadLine(); } } } \ No newline at end of file diff --git a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs index f381820..9b7f4cb 100644 --- a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs +++ b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -1125,5 +1126,93 @@ public void TryGetReturnsCachedValueAndTrue() Assert.IsFalse(contains2); } + + [Test] + public void ListOfCacheKeysContainsAllKeysAfterCallingAdd() + { + List keys = new List(){"one", "two", "three", "four", "five"}; + + + foreach (var key in keys) + { + sut.Add(key, key.Reverse()); + } + + var cachedKeys = sut.GetCacheKeys().Keys; + + Assert.IsTrue(keys.Intersect(cachedKeys).Count() == keys.Count); + } + + [Test] + public void ListOfCacheKeysContainsAllKeysAfterCallingGetOrAdd() + { + List keys = new List() { "one", "two", "three", "four", "five" }; + + foreach (var key in keys) + { + sut.GetOrAdd(key, () => key.Reverse()); + } + + var cachedKeys = sut.GetCacheKeys().Keys; + + Assert.IsTrue(keys.Intersect(cachedKeys).Count() == keys.Count); + } + + [Test] + public void ListOfCacheKeysContainsSomeKeysAfterCallingAddAndRemovingOne() + { + List keys = new List() { "one", "two", "three", "four", "five" }; + + foreach (var key in keys) + { + sut.Add(key, key.Reverse()); + } + + // remove three + sut.Remove("three"); + + var cachedKeys = sut.GetCacheKeys().Keys.ToList(); + + Assert.IsTrue(!cachedKeys.Contains("three"), "Three should be gone"); + } + + [Test] + public void ListOfCacheKeysContainsAllKeysAfterCallingGetOrAddAndRemovingOne() + { + List keys = new List() { "one", "two", "three", "four", "five" }; + + foreach (var key in keys) + { + sut.GetOrAdd(key, () => key.Reverse()); + } + + // remove three + sut.Remove("three"); + + var cachedKeys = sut.GetCacheKeys().Keys.ToList(); + + Assert.IsTrue(!cachedKeys.Contains("three"), "Three should be gone"); + } + + public void ListOfCacheKeysIsEmptyAfterRemovingThemAll() + { + List keys = new List() {"one", "two", "three", "four", "five"}; + + foreach (var key in keys) + { + sut.GetOrAdd(key, () => key.Reverse()); + } + + var cachedKeys = sut.GetCacheKeys().Keys.ToList(); + Assert.IsTrue(keys.Intersect(cachedKeys).Count() == keys.Count); + + foreach (var key in cachedKeys) + { + sut.Remove(key); + } + + var cachedKeys2 = sut.GetCacheKeys().Keys.ToList(); + Assert.AreEqual(cachedKeys2.Count, 0, "Should be zero keys left"); + } } } \ No newline at end of file diff --git a/LazyCache/CachedItemMeta.cs b/LazyCache/CachedItemMeta.cs new file mode 100644 index 0000000..2c68403 --- /dev/null +++ b/LazyCache/CachedItemMeta.cs @@ -0,0 +1,14 @@ +using System; + +namespace LazyCache +{ + public class CachedItemMeta + { + public CachedItemMeta() + { + this.CreatedDate = DateTime.UtcNow; + } + + public DateTime CreatedDate { get; set; } + } +} diff --git a/LazyCache/CachingService.cs b/LazyCache/CachingService.cs index 6becdd3..2921991 100644 --- a/LazyCache/CachingService.cs +++ b/LazyCache/CachingService.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using LazyCache.Providers; @@ -15,6 +17,8 @@ public class CachingService : IAppCache private readonly int[] keyLocks; + private ConcurrentDictionary cacheKeyDictionary; + public CachingService() : this(DefaultCacheProvider) { } @@ -24,6 +28,7 @@ public CachingService(Lazy cacheProvider) this.cacheProvider = cacheProvider ?? throw new ArgumentNullException(nameof(cacheProvider)); var lockCount = Math.Max(Environment.ProcessorCount * 8, 32); keyLocks = new int[lockCount]; + cacheKeyDictionary = new ConcurrentDictionary(); } public CachingService(Func cacheProviderFactory) @@ -32,7 +37,7 @@ public CachingService(Func cacheProviderFactory) cacheProvider = new Lazy(cacheProviderFactory); var lockCount = Math.Max(Environment.ProcessorCount * 8, 32); keyLocks = new int[lockCount]; - + cacheKeyDictionary = new ConcurrentDictionary(); } public CachingService(ICacheProvider cache) : this(() => cache) @@ -69,6 +74,7 @@ public virtual void Add(string key, T item, MemoryCacheEntryOptions policy) ValidateKey(key); CacheProvider.Set(key, item, policy); + RememberCacheKey(key); } public virtual T Get(string key) @@ -113,6 +119,7 @@ object CacheFactory(ICacheEntry entry) => var result = addItemFactory(entry); SetAbsoluteExpirationFromRelative(entry); EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy(entry.PostEvictionCallbacks); + RememberCacheKey(entry.Key.ToString()); return result; }); @@ -137,6 +144,7 @@ object CacheFactory(ICacheEntry entry) => if (valueHasChangedType) { CacheProvider.Remove(key); + this.RemoveRememberedCacheKey(key); // acquire lock again hash = (uint)key.GetHashCode() % (uint)keyLocks.Length; @@ -158,6 +166,7 @@ object CacheFactory(ICacheEntry entry) => catch //addItemFactory errored so do not cache the exception { CacheProvider.Remove(key); + this.RemoveRememberedCacheKey(key); throw; } } @@ -175,6 +184,7 @@ public virtual void Remove(string key) { ValidateKey(key); CacheProvider.Remove(key); + RemoveRememberedCacheKey(key); } public virtual ICacheProvider CacheProvider => cacheProvider.Value; @@ -206,6 +216,7 @@ object CacheFactory(ICacheEntry entry) => var result = addItemFactory(entry); SetAbsoluteExpirationFromRelative(entry); EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy(entry.PostEvictionCallbacks); + RememberCacheKey(entry.Key.ToString()); return result; }); @@ -226,6 +237,7 @@ object CacheFactory(ICacheEntry entry) => if (valueHasChangedType) { CacheProvider.Remove(key); + this.RemoveRememberedCacheKey(key); // acquire lock hash = (uint)key.GetHashCode() % (uint)keyLocks.Length; @@ -244,17 +256,26 @@ object CacheFactory(ICacheEntry entry) => if (result.IsCanceled || result.IsFaulted) + { CacheProvider.Remove(key); + this.RemoveRememberedCacheKey(key); + } return await result.ConfigureAwait(false); } catch //addItemFactory errored so do not cache the exception { CacheProvider.Remove(key); + this.RemoveRememberedCacheKey(key); throw; } } + public Dictionary GetCacheKeys() + { + return cacheKeyDictionary.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + protected virtual T GetValueFromLazy(object item, out bool valueHasChangedType) { valueHasChangedType = false; @@ -342,5 +363,17 @@ protected virtual void ValidateKey(string key) if (string.IsNullOrWhiteSpace(key)) throw new ArgumentOutOfRangeException(nameof(key), "Cache keys cannot be empty or whitespace"); } + + protected void RememberCacheKey(string key) + { + var meta = new CachedItemMeta(); + cacheKeyDictionary.AddOrUpdate(key, meta, (oldKey, oldValue) => meta); + } + + protected void RemoveRememberedCacheKey(string key) + { + CachedItemMeta remove; + cacheKeyDictionary.TryRemove(key, out remove); + } } } \ No newline at end of file diff --git a/LazyCache/IAppCache.cs b/LazyCache/IAppCache.cs index 3d9133d..83cb406 100644 --- a/LazyCache/IAppCache.cs +++ b/LazyCache/IAppCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -22,5 +23,6 @@ public interface IAppCache Task GetOrAddAsync(string key, Func> addItemFactory); Task GetOrAddAsync(string key, Func> addItemFactory, MemoryCacheEntryOptions policy); void Remove(string key); + Dictionary GetCacheKeys(); } } \ No newline at end of file diff --git a/LazyCache/Mocks/MockCachingService.cs b/LazyCache/Mocks/MockCachingService.cs index 9c9be86..23c3e1d 100644 --- a/LazyCache/Mocks/MockCachingService.cs +++ b/LazyCache/Mocks/MockCachingService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -11,6 +12,7 @@ namespace LazyCache.Mocks public class MockCachingService : IAppCache { public ICacheProvider CacheProvider { get; } = new MockCacheProvider(); + public CacheDefaults DefaultCachePolicy { get; set; } = new CacheDefaults(); public T Get(string key) @@ -57,5 +59,10 @@ public bool TryGetValue(string key, out object value) value = default(T); return true; } + + public Dictionary GetCacheKeys() + { + return new Dictionary(); + } } } \ No newline at end of file