From 9aa47d0017819ef572352975647be049e9b7e7b5 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 13 Oct 2022 23:23:09 +1030 Subject: [PATCH 1/8] Disable default behaviour of protobuf-net See issue: https://github.com/protobuf-net/protobuf-net/issues/964 --- .../ProtobufCacheSerializer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs b/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs index aa2e57eb..0f8fa90f 100644 --- a/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs +++ b/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs @@ -18,7 +18,7 @@ public class ProtobufCacheSerializer : ICacheSerializer { static ProtobufCacheSerializer() { - RuntimeTypeModel.Default.Add() + RuntimeTypeModel.Default.Add(applyDefaultBehaviour: false) .Add(1, nameof(ManifestEntry.FileName)) .Add(2, nameof(ManifestEntry.Expiry)); } @@ -36,7 +36,7 @@ static SerializerConfig() { if (typeof(ICacheEntry).IsAssignableFrom(typeof(T))) { - RuntimeTypeModel.Default.Add(typeof(T)) + RuntimeTypeModel.Default.Add(typeof(T), applyDefaultBehaviour: false) .Add(1, nameof(CacheEntry.Expiry)) .Add(2, nameof(CacheEntry.Value)); } From db2f191991c2c8bdcd5a9a9332541599e86701de Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 00:22:08 +0930 Subject: [PATCH 2/8] Catch and log serialization exceptions --- .../NewtonsoftJsonCacheSerializer.cs | 29 +++++++++--- .../ProtobufCacheSerializer.cs | 25 ++++++++-- .../SystemTextJsonCacheSerializer.cs | 21 +++++++-- src/CacheTower/CacheContextActivators.cs | 35 -------------- src/CacheTower/CacheStack.cs | 43 +++++++++++++---- src/CacheTower/CacheStack{TContext}.cs | 17 ++----- src/CacheTower/CacheTower.csproj | 1 + .../CacheSerializationException.cs | 25 ++++++++++ src/CacheTower/ServiceCollectionExtensions.cs | 3 ++ .../CacheStackContextTests.cs | 46 +++++++++++++++---- 10 files changed, 163 insertions(+), 82 deletions(-) create mode 100644 src/CacheTower/Serializers/CacheSerializationException.cs diff --git a/src/CacheTower.Serializers.NewtonsoftJson/NewtonsoftJsonCacheSerializer.cs b/src/CacheTower.Serializers.NewtonsoftJson/NewtonsoftJsonCacheSerializer.cs index 4ccd0e87..82078978 100644 --- a/src/CacheTower.Serializers.NewtonsoftJson/NewtonsoftJsonCacheSerializer.cs +++ b/src/CacheTower.Serializers.NewtonsoftJson/NewtonsoftJsonCacheSerializer.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text; using Newtonsoft.Json; @@ -32,16 +33,30 @@ public NewtonsoftJsonCacheSerializer(JsonSerializerSettings settings) /// public void Serialize(Stream stream, T? value) { - using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true); - using var jsonWriter = new JsonTextWriter(streamWriter); - serializer.Serialize(jsonWriter, value); + try + { + using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true); + using var jsonWriter = new JsonTextWriter(streamWriter); + serializer.Serialize(jsonWriter, value); + } + catch (Exception ex) + { + throw new CacheSerializationException("A serialization error has occurred when serializing with Newtonsoft.Json", ex); + } } /// public T? Deserialize(Stream stream) { - using var streamReader = new StreamReader(stream, Encoding.UTF8, false, 1024); - using var jsonReader = new JsonTextReader(streamReader); - return serializer.Deserialize(jsonReader); + try + { + using var streamReader = new StreamReader(stream, Encoding.UTF8, false, 1024); + using var jsonReader = new JsonTextReader(streamReader); + return serializer.Deserialize(jsonReader); + } + catch (Exception ex) + { + throw new CacheSerializationException("A serialization error has occurred when deserializing with Newtonsoft.Json", ex); + } } } diff --git a/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs b/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs index 0f8fa90f..a482abfd 100644 --- a/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs +++ b/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using CacheTower.Providers.FileSystem; using ProtoBuf; using ProtoBuf.Meta; @@ -49,15 +50,29 @@ public static void EnsureConfigured() { } /// public void Serialize(Stream stream, T? value) { - SerializerConfig.EnsureConfigured(); - Serializer.Serialize(stream, value); + try + { + SerializerConfig.EnsureConfigured(); + Serializer.Serialize(stream, value); + } + catch (Exception ex) + { + throw new CacheSerializationException("A serialization error has occurred when serializing with ProtoBuf", ex); + } } /// public T? Deserialize(Stream stream) { - SerializerConfig.EnsureConfigured(); - return Serializer.Deserialize(stream); + try + { + SerializerConfig.EnsureConfigured(); + return Serializer.Deserialize(stream); + } + catch (Exception ex) + { + throw new CacheSerializationException("A serialization error has occurred when deserializing with ProtoBuf", ex); + } } } } diff --git a/src/CacheTower.Serializers.SystemTextJson/SystemTextJsonCacheSerializer.cs b/src/CacheTower.Serializers.SystemTextJson/SystemTextJsonCacheSerializer.cs index d993d966..57797ac0 100644 --- a/src/CacheTower.Serializers.SystemTextJson/SystemTextJsonCacheSerializer.cs +++ b/src/CacheTower.Serializers.SystemTextJson/SystemTextJsonCacheSerializer.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text.Json; namespace CacheTower.Serializers.SystemTextJson; @@ -28,12 +29,26 @@ public SystemTextJsonCacheSerializer(JsonSerializerOptions options) /// public void Serialize(Stream stream, T? value) { - JsonSerializer.Serialize(stream, value, options); + try + { + JsonSerializer.Serialize(stream, value, options); + } + catch (Exception ex) + { + throw new CacheSerializationException("A serialization error has occurred when serializing with System.Text.Json", ex); + } } /// public T? Deserialize(Stream stream) { - return JsonSerializer.Deserialize(stream, options); + try + { + return JsonSerializer.Deserialize(stream, options); + } + catch (Exception ex) + { + throw new CacheSerializationException("A serialization error has occurred when deserializing with System.Text.Json", ex); + } } } \ No newline at end of file diff --git a/src/CacheTower/CacheContextActivators.cs b/src/CacheTower/CacheContextActivators.cs index 6a9af3fd..e34280fc 100644 --- a/src/CacheTower/CacheContextActivators.cs +++ b/src/CacheTower/CacheContextActivators.cs @@ -36,39 +36,4 @@ public void Dispose() { ServiceScope.Dispose(); } -} - - -internal class FuncCacheContextActivator : ICacheContextActivator -{ - private readonly Func Resolver; - - public FuncCacheContextActivator(Func resolver) - { - Resolver = resolver; - } - - public ICacheContextScope BeginScope() - { - return new FuncCacheContextScope(Resolver); - } -} - -internal class FuncCacheContextScope : ICacheContextScope -{ - private readonly Func Resolver; - - public FuncCacheContextScope(Func resolver) - { - Resolver = resolver; - } - - public object Resolve(Type type) - { - return Resolver()!; - } - - public void Dispose() - { - } } \ No newline at end of file diff --git a/src/CacheTower/CacheStack.cs b/src/CacheTower/CacheStack.cs index c9490905..f461afde 100644 --- a/src/CacheTower/CacheStack.cs +++ b/src/CacheTower/CacheStack.cs @@ -4,6 +4,8 @@ using System.Threading.Tasks; using CacheTower.Extensions; using CacheTower.Internal; +using CacheTower.Serializers; +using Microsoft.Extensions.Logging; namespace CacheTower { @@ -15,21 +17,24 @@ public class CacheStack : ICacheStack, IFlushableCacheStack, IExtendableCacheSta private bool Disposed; private readonly CacheEntryKeyLock KeyLock = new(); + private readonly ILogger Logger; private readonly ICacheLayer[] CacheLayers; private readonly ExtensionContainer Extensions; /// /// Creates a new with the given and . /// + /// The internal logger to use. /// The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required. /// The cache extensions to use for the current cache stack. - public CacheStack(ICacheLayer[] cacheLayers, ICacheExtension[] extensions) + public CacheStack(ILogger logger, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) { if (cacheLayers == null || cacheLayers.Length == 0) { throw new ArgumentException("There must be at least one cache layer", nameof(cacheLayers)); } + Logger = logger; CacheLayers = cacheLayers; Extensions = new ExtensionContainer(extensions); @@ -139,7 +144,15 @@ private async ValueTask InternalSetAsync(string cacheKey, CacheEntry cache for (int i = 0, l = CacheLayers.Length; i < l; i++) { var layer = CacheLayers[i]; - await layer.SetAsync(cacheKey, cacheEntry); + + try + { + await layer.SetAsync(cacheKey, cacheEntry); + } + catch (CacheSerializationException ex) + { + Logger.LogWarning(ex, "Unable to set CacheKey={CacheKey} on CacheLayer={CacheLayer} due to an exception. This will result in cache misses on this layer.", cacheKey, layer.GetType()); + } } await Extensions.OnCacheUpdateAsync(cacheKey, cacheEntry.Expiry, cacheUpdateType); @@ -160,10 +173,17 @@ private async ValueTask InternalSetAsync(string cacheKey, CacheEntry cache var layer = CacheLayers[layerIndex]; if (await layer.IsAvailableAsync(cacheKey)) { - var cacheEntry = await layer.GetAsync(cacheKey); - if (cacheEntry != default) + try + { + var cacheEntry = await layer.GetAsync(cacheKey); + if (cacheEntry != default) + { + return cacheEntry; + } + } + catch (CacheSerializationException ex) { - return cacheEntry; + Logger.LogWarning(ex, "Unable to retrieve CacheKey={CacheKey} from CacheLayer={CacheLayer} due to an exception. This layer will be skipped.", cacheKey, layer.GetType()); } } } @@ -179,10 +199,17 @@ private async ValueTask InternalSetAsync(string cacheKey, CacheEntry cache var layer = CacheLayers[layerIndex]; if (await layer.IsAvailableAsync(cacheKey)) { - var cacheEntry = await layer.GetAsync(cacheKey); - if (cacheEntry != default) + try + { + var cacheEntry = await layer.GetAsync(cacheKey); + if (cacheEntry != default) + { + return (layerIndex, cacheEntry); + } + } + catch (CacheSerializationException ex) { - return (layerIndex, cacheEntry); + Logger.LogWarning(ex, "Unable to retrieve CacheKey={CacheKey} from CacheLayer={CacheLayer} due to an exception. This layer will be skipped.", cacheKey, layer.GetType()); } } } diff --git a/src/CacheTower/CacheStack{TContext}.cs b/src/CacheTower/CacheStack{TContext}.cs index 541fc578..eaac46c3 100644 --- a/src/CacheTower/CacheStack{TContext}.cs +++ b/src/CacheTower/CacheStack{TContext}.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace CacheTower { @@ -13,26 +14,14 @@ public class CacheStack : CacheStack, ICacheStack { private readonly ICacheContextActivator CacheContextActivator; - /// - /// Creates a new with the given , and . - /// - /// The factory that provides the context. This is called for every cache item refresh. - /// The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required. - /// The cache extensions to use for the current cache stack. - [Obsolete("Use other constructor that requires an `ICacheContextActivator` instead. This constructor will be removed in a future version.")] - public CacheStack(Func contextFactory, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(cacheLayers, extensions) - { - var factory = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory)); - CacheContextActivator = new FuncCacheContextActivator(factory); - } - /// /// Creates a new with the given , and . /// + /// The internal logger to use. /// The activator that provides the context. This is called for every cache item refresh. /// The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required. /// The cache extensions to use for the current cache stack. - public CacheStack(ICacheContextActivator cacheContextActivator, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(cacheLayers, extensions) + public CacheStack(ILogger logger, ICacheContextActivator cacheContextActivator, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(logger, cacheLayers, extensions) { CacheContextActivator = cacheContextActivator ?? throw new ArgumentNullException(nameof(cacheContextActivator)); } diff --git a/src/CacheTower/CacheTower.csproj b/src/CacheTower/CacheTower.csproj index ee9fc4fd..54e09384 100644 --- a/src/CacheTower/CacheTower.csproj +++ b/src/CacheTower/CacheTower.csproj @@ -11,6 +11,7 @@ + diff --git a/src/CacheTower/Serializers/CacheSerializationException.cs b/src/CacheTower/Serializers/CacheSerializationException.cs new file mode 100644 index 00000000..9b569efc --- /dev/null +++ b/src/CacheTower/Serializers/CacheSerializationException.cs @@ -0,0 +1,25 @@ +using System; + +namespace CacheTower.Serializers; + +/// +/// An exception for any cache serialization exceptions that occur. +/// +public class CacheSerializationException : Exception +{ + /// + /// Creates a new . + /// + public CacheSerializationException() : base() { } + /// + /// Creates a new with the specified . + /// + /// The error message + public CacheSerializationException(string message) : base(message) { } + /// + /// Creates a new with the specified and . + /// + /// The error message + /// The inner exception + public CacheSerializationException(string message, Exception innerException) : base(message, innerException) { } +} diff --git a/src/CacheTower/ServiceCollectionExtensions.cs b/src/CacheTower/ServiceCollectionExtensions.cs index 41a43a96..628288b1 100644 --- a/src/CacheTower/ServiceCollectionExtensions.cs +++ b/src/CacheTower/ServiceCollectionExtensions.cs @@ -7,6 +7,7 @@ using CacheTower.Providers.FileSystem; using CacheTower.Providers.Memory; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.DependencyInjection; @@ -80,6 +81,7 @@ private static ICacheStack BuildCacheStack(IServiceProvider provider, Action>(), builder.CacheLayers.ToArray(), builder.Extensions.ToArray() ); @@ -91,6 +93,7 @@ private static ICacheStack BuildCacheStack(IServiceProvider configureBuilder(provider, builder); ThrowIfInvalidBuilder(builder); return new CacheStack( + provider.GetRequiredService>(), builder.CacheContextActivator, builder.CacheLayers.ToArray(), builder.Extensions.ToArray() diff --git a/tests/CacheTower.Tests/CacheStackContextTests.cs b/tests/CacheTower.Tests/CacheStackContextTests.cs index 13cccc49..deff1f4e 100644 --- a/tests/CacheTower.Tests/CacheStackContextTests.cs +++ b/tests/CacheTower.Tests/CacheStackContextTests.cs @@ -1,38 +1,64 @@ using CacheTower.Providers.Memory; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Logging.Abstractions; namespace CacheTower.Tests { [TestClass] public class CacheStackContextTests : TestBase { - [TestMethod, ExpectedException(typeof(ArgumentNullException))] - public void ConstructorThrowsOnNullContextFactory() + internal class FuncCacheContextActivator : ICacheContextActivator + { + private readonly Func Resolver; + + public FuncCacheContextActivator(Func resolver) + { + Resolver = resolver; + } + + public ICacheContextScope BeginScope() + { + return new FuncCacheContextScope(Resolver); + } + } + + internal class FuncCacheContextScope : ICacheContextScope { - new CacheStack((Func) null, new[] { new MemoryCacheLayer() }, Array.Empty()); + private readonly Func Resolver; + + public FuncCacheContextScope(Func resolver) + { + Resolver = resolver; + } + + public object Resolve(Type type) + { + return Resolver()!; + } + + public void Dispose() + { + } } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task GetOrSet_ThrowsOnNullKey() { - await using var cacheStack = new CacheStack(() => null, new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => null), new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetOrSetAsync(null, (old, context) => Task.FromResult(5), new CacheSettings(TimeSpan.FromDays(1))); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task GetOrSet_ThrowsOnNullGetter() { - await using var cacheStack = new CacheStack(() => null, new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => null), new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetOrSetAsync("MyCacheKey", null, new CacheSettings(TimeSpan.FromDays(1))); } [TestMethod] public async Task GetOrSet_CacheMiss_ContextHasValue() { - await using var cacheStack = new CacheStack(() => 123, new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => 123), new[] { new MemoryCacheLayer() }, Array.Empty()); var result = await cacheStack.GetOrSetAsync("GetOrSet_CacheMiss_ContextHasValue", (oldValue, context) => { Assert.AreEqual(123, context); @@ -45,7 +71,7 @@ public async Task GetOrSet_CacheMiss_ContextHasValue() public async Task GetOrSet_CacheMiss_ContextFactoryCalledEachTime() { var contextValue = 0; - await using var cacheStack = new CacheStack(() => contextValue++, new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => contextValue++), new[] { new MemoryCacheLayer() }, Array.Empty()); var result1 = await cacheStack.GetOrSetAsync("GetOrSet_CacheMiss_ContextFactoryCalledEachTime_1", (oldValue, context) => { From 21d2fa58d9628241541b0c6cf57ee1e985cc647c Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 22:17:34 +0930 Subject: [PATCH 3/8] Remove obsolete service collection methods --- src/CacheTower/ServiceCollectionExtensions.cs | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/src/CacheTower/ServiceCollectionExtensions.cs b/src/CacheTower/ServiceCollectionExtensions.cs index 628288b1..c9c4830b 100644 --- a/src/CacheTower/ServiceCollectionExtensions.cs +++ b/src/CacheTower/ServiceCollectionExtensions.cs @@ -236,70 +236,4 @@ private static void AddRange(this ICollection collection, IEnumerable v collection.Add(value); } } - - /// - /// Adds a singleton to the specified with the given layers and automatic cleanup frequency (via ). - /// - /// The services collection to add the dependencies to. - /// The cache layers to use. - /// The frequency at which cache stack cleanup will be performed. - [Obsolete("Use service collection extension with builder pattern instead. This will be removed in a future version.")] - public static void AddCacheStack(this IServiceCollection services, ICacheLayer[] layers, TimeSpan cleanupFrequency) - { - services.AddCacheStack(builder => - { - builder.CacheLayers.AddRange(layers); - builder.WithCleanupFrequency(cleanupFrequency); - }); - } - - /// - /// Adds a singleton to the specified with the specified layers and extensions. - /// - /// The services collection to add the dependencies to. - /// The cache layers to use. - /// The cache extensions to use. - [Obsolete("Use service collection extension with builder pattern instead. This will be removed in a future version.")] - public static void AddCacheStack(this IServiceCollection services, ICacheLayer[] layers, ICacheExtension[] extensions) - { - services.AddCacheStack(builder => - { - builder.CacheLayers.AddRange(layers); - builder.Extensions.AddRange(extensions); - }); - } - - /// - /// Adds a singleton to the specified with the specified layers and extensions. - /// An implementation factory of is built using the established when instantiating the . - /// - /// The services collection to add the dependencies to. - /// The cache layers to use. - /// The cache extensions to use. - [Obsolete("Use service collection extension with builder pattern instead. This will be removed in a future version.")] - public static void AddCacheStack(this IServiceCollection services, ICacheLayer[] layers, ICacheExtension[] extensions) - { - services.AddCacheStack(builder => - { - builder.CacheLayers.AddRange(layers); - builder.Extensions.AddRange(extensions); - }); - } - - /// - /// Adds a singleton to the specified with the specified , layers and extensions. - /// - /// The services collection to add the dependencies to. - /// The factory method that will generate a context when is called. - /// The cache layers to use. - /// The cache extensions to use. - [Obsolete("Use service collection extension with builder pattern instead. This will be removed in a future version.")] - public static void AddCacheStack(this IServiceCollection services, Func contextFactory, ICacheLayer[] layers, ICacheExtension[] extensions) - { - services.AddCacheStack(new FuncCacheContextActivator(contextFactory), builder => - { - builder.CacheLayers.AddRange(layers); - builder.Extensions.AddRange(extensions); - }); - } } \ No newline at end of file From f64c73bb10ad63f0d8a82df37128dae4c638a8ea Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 22:44:15 +0930 Subject: [PATCH 4/8] Allow logging to be optional --- src/CacheTower/CacheStack.cs | 5 +++-- src/CacheTower/CacheStack{TContext}.cs | 2 +- src/CacheTower/ServiceCollectionExtensions.cs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/CacheTower/CacheStack.cs b/src/CacheTower/CacheStack.cs index f461afde..441491e5 100644 --- a/src/CacheTower/CacheStack.cs +++ b/src/CacheTower/CacheStack.cs @@ -6,6 +6,7 @@ using CacheTower.Internal; using CacheTower.Serializers; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace CacheTower { @@ -27,14 +28,14 @@ public class CacheStack : ICacheStack, IFlushableCacheStack, IExtendableCacheSta /// The internal logger to use. /// The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required. /// The cache extensions to use for the current cache stack. - public CacheStack(ILogger logger, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) + public CacheStack(ILogger? logger, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) { if (cacheLayers == null || cacheLayers.Length == 0) { throw new ArgumentException("There must be at least one cache layer", nameof(cacheLayers)); } - Logger = logger; + Logger = logger ?? NullLogger.Instance; CacheLayers = cacheLayers; Extensions = new ExtensionContainer(extensions); diff --git a/src/CacheTower/CacheStack{TContext}.cs b/src/CacheTower/CacheStack{TContext}.cs index eaac46c3..c6a19442 100644 --- a/src/CacheTower/CacheStack{TContext}.cs +++ b/src/CacheTower/CacheStack{TContext}.cs @@ -21,7 +21,7 @@ public class CacheStack : CacheStack, ICacheStack /// The activator that provides the context. This is called for every cache item refresh. /// The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required. /// The cache extensions to use for the current cache stack. - public CacheStack(ILogger logger, ICacheContextActivator cacheContextActivator, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(logger, cacheLayers, extensions) + public CacheStack(ILogger? logger, ICacheContextActivator cacheContextActivator, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(logger, cacheLayers, extensions) { CacheContextActivator = cacheContextActivator ?? throw new ArgumentNullException(nameof(cacheContextActivator)); } diff --git a/src/CacheTower/ServiceCollectionExtensions.cs b/src/CacheTower/ServiceCollectionExtensions.cs index c9c4830b..ba0072f2 100644 --- a/src/CacheTower/ServiceCollectionExtensions.cs +++ b/src/CacheTower/ServiceCollectionExtensions.cs @@ -81,7 +81,7 @@ private static ICacheStack BuildCacheStack(IServiceProvider provider, Action>(), + provider.GetService>(), builder.CacheLayers.ToArray(), builder.Extensions.ToArray() ); @@ -93,7 +93,7 @@ private static ICacheStack BuildCacheStack(IServiceProvider configureBuilder(provider, builder); ThrowIfInvalidBuilder(builder); return new CacheStack( - provider.GetRequiredService>(), + provider.GetService>(), builder.CacheContextActivator, builder.CacheLayers.ToArray(), builder.Extensions.ToArray() From 550b2784e5529ff5bdb1708b405daa560396d429 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 22:48:58 +0930 Subject: [PATCH 5/8] Fix test compilation errors --- .../CacheContextActivatorTests.cs | 7 +- .../CacheStackContextTests.cs | 43 ++----------- tests/CacheTower.Tests/CacheStackTests.cs | 64 +++++++++---------- .../Extensions/AutoCleanupExtensionTests.cs | 36 +++++------ .../RedisRemoteEvictionExtensionTests.cs | 2 +- .../FuncCacheContextActivator.cs | 37 +++++++++++ 6 files changed, 90 insertions(+), 99 deletions(-) create mode 100644 tests/CacheTower.Tests/FuncCacheContextActivator.cs diff --git a/tests/CacheTower.Tests/CacheContextActivatorTests.cs b/tests/CacheTower.Tests/CacheContextActivatorTests.cs index d98abec7..692d4d6b 100644 --- a/tests/CacheTower.Tests/CacheContextActivatorTests.cs +++ b/tests/CacheTower.Tests/CacheContextActivatorTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CacheTower.Tests; diff --git a/tests/CacheTower.Tests/CacheStackContextTests.cs b/tests/CacheTower.Tests/CacheStackContextTests.cs index deff1f4e..e24f4458 100644 --- a/tests/CacheTower.Tests/CacheStackContextTests.cs +++ b/tests/CacheTower.Tests/CacheStackContextTests.cs @@ -2,63 +2,28 @@ using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.Extensions.Logging.Abstractions; namespace CacheTower.Tests { [TestClass] public class CacheStackContextTests : TestBase { - internal class FuncCacheContextActivator : ICacheContextActivator - { - private readonly Func Resolver; - - public FuncCacheContextActivator(Func resolver) - { - Resolver = resolver; - } - - public ICacheContextScope BeginScope() - { - return new FuncCacheContextScope(Resolver); - } - } - - internal class FuncCacheContextScope : ICacheContextScope - { - private readonly Func Resolver; - - public FuncCacheContextScope(Func resolver) - { - Resolver = resolver; - } - - public object Resolve(Type type) - { - return Resolver()!; - } - - public void Dispose() - { - } - } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task GetOrSet_ThrowsOnNullKey() { - await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => null), new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new FuncCacheContextActivator(() => null), new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetOrSetAsync(null, (old, context) => Task.FromResult(5), new CacheSettings(TimeSpan.FromDays(1))); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task GetOrSet_ThrowsOnNullGetter() { - await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => null), new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new FuncCacheContextActivator(() => null), new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetOrSetAsync("MyCacheKey", null, new CacheSettings(TimeSpan.FromDays(1))); } [TestMethod] public async Task GetOrSet_CacheMiss_ContextHasValue() { - await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => 123), new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new FuncCacheContextActivator(() => 123), new[] { new MemoryCacheLayer() }, Array.Empty()); var result = await cacheStack.GetOrSetAsync("GetOrSet_CacheMiss_ContextHasValue", (oldValue, context) => { Assert.AreEqual(123, context); @@ -71,7 +36,7 @@ public async Task GetOrSet_CacheMiss_ContextHasValue() public async Task GetOrSet_CacheMiss_ContextFactoryCalledEachTime() { var contextValue = 0; - await using var cacheStack = new CacheStack(NullLogger.Instance, new FuncCacheContextActivator(() => contextValue++), new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new FuncCacheContextActivator(() => contextValue++), new[] { new MemoryCacheLayer() }, Array.Empty()); var result1 = await cacheStack.GetOrSetAsync("GetOrSet_CacheMiss_ContextFactoryCalledEachTime_1", (oldValue, context) => { diff --git a/tests/CacheTower.Tests/CacheStackTests.cs b/tests/CacheTower.Tests/CacheStackTests.cs index 2fa3a727..af4933d0 100644 --- a/tests/CacheTower.Tests/CacheStackTests.cs +++ b/tests/CacheTower.Tests/CacheStackTests.cs @@ -15,17 +15,17 @@ public class CacheStackTests : TestBase [TestMethod, ExpectedException(typeof(ArgumentException))] public void ConstructorThrowsOnNullCacheLayer() { - new CacheStack(null, Array.Empty()); + new CacheStack(null, null, Array.Empty()); } [TestMethod, ExpectedException(typeof(ArgumentException))] public void ConstructorThrowsOnEmptyCacheLayer() { - new CacheStack(Array.Empty(), Array.Empty()); + new CacheStack(null, Array.Empty(), Array.Empty()); } [TestMethod] public async Task ConstructorAllowsNullExtensions() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); } [TestMethod] @@ -34,7 +34,7 @@ public async Task Cleanup_CleansAllTheLayers() var layer1 = new MemoryCacheLayer(); var layer2 = new MemoryCacheLayer(); - await using var cacheStack = new CacheStack(new[] { layer1, layer2 }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { layer1, layer2 }, Array.Empty()); var cacheEntry = new CacheEntry(42, DateTime.UtcNow.AddDays(-1)); await cacheStack.SetAsync("Cleanup_CleansAllTheLayers", cacheEntry); @@ -50,7 +50,7 @@ public async Task Cleanup_CleansAllTheLayers() [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task Cleanup_ThrowsOnUseAfterDisposal() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -60,7 +60,7 @@ public async Task Cleanup_ThrowsOnUseAfterDisposal() [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task Evict_ThrowsOnNullKey() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.EvictAsync(null); } [TestMethod] @@ -69,7 +69,7 @@ public async Task Evict_EvictsAllTheLayers() var layer1 = new MemoryCacheLayer(); var layer2 = new MemoryCacheLayer(); - await using var cacheStack = new CacheStack(new[] { layer1, layer2 }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { layer1, layer2 }, Array.Empty()); var cacheEntry = await cacheStack.SetAsync("Evict_EvictsAllTheLayers", 42, TimeSpan.FromDays(1)); Assert.AreEqual(cacheEntry, await layer1.GetAsync("Evict_EvictsAllTheLayers")); @@ -84,7 +84,7 @@ public async Task Evict_EvictsAllTheLayers() public async Task Evict_TriggersCacheChangeExtension() { var mockExtension = new Mock(); - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, new[] { mockExtension.Object }); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, new[] { mockExtension.Object }); var cacheEntry = await cacheStack.SetAsync("Evict_TriggerCacheChangeExtension", 42, TimeSpan.FromDays(1)); await cacheStack.EvictAsync("Evict_TriggerCacheChangeExtension"); @@ -94,7 +94,7 @@ public async Task Evict_TriggersCacheChangeExtension() [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task Evict_ThrowsOnUseAfterDisposal() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -108,7 +108,7 @@ public async Task Flush_FlushesAllTheLayers() var layer1 = new MemoryCacheLayer(); var layer2 = new MemoryCacheLayer(); - await using var cacheStack = new CacheStack(new[] { layer1, layer2 }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { layer1, layer2 }, Array.Empty()); var cacheEntry = await cacheStack.SetAsync("Flush_FlushesAllTheLayers", 42, TimeSpan.FromDays(1)); Assert.AreEqual(cacheEntry, await layer1.GetAsync("Flush_FlushesAllTheLayers")); @@ -123,7 +123,7 @@ public async Task Flush_FlushesAllTheLayers() public async Task Flush_TriggersCacheChangeExtension() { var mockExtension = new Mock(); - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, new[] { mockExtension.Object }); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, new[] { mockExtension.Object }); await cacheStack.FlushAsync(); @@ -132,7 +132,7 @@ public async Task Flush_TriggersCacheChangeExtension() [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task Flush_ThrowsOnUseAfterDisposal() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -142,13 +142,13 @@ public async Task Flush_ThrowsOnUseAfterDisposal() [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task Get_ThrowsOnNullKey() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetAsync(null); } [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task Get_ThrowsOnUseAfterDisposal() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -158,20 +158,20 @@ public async Task Get_ThrowsOnUseAfterDisposal() [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task Set_ThrowsOnNullKey() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.SetAsync(null, new CacheEntry(1, TimeSpan.FromDays(1))); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task Set_ThrowsOnNullCacheEntry() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.SetAsync("MyCacheKey", (CacheEntry)null); } [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task Set_ThrowsOnUseAfterDisposal() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -180,7 +180,7 @@ public async Task Set_ThrowsOnUseAfterDisposal() [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task Set_ThrowsOnUseAfterDisposal_CacheEntry() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -192,7 +192,7 @@ public async Task Set_SetsAllTheLayers() var layer1 = new MemoryCacheLayer(); var layer2 = new MemoryCacheLayer(); - await using var cacheStack = new CacheStack(new[] { layer1, layer2 }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { layer1, layer2 }, Array.Empty()); var cacheEntry = await cacheStack.SetAsync("Set_SetsAllTheLayers", 42, TimeSpan.FromDays(1)); Assert.AreEqual(cacheEntry, await layer1.GetAsync("Set_SetsAllTheLayers")); @@ -202,7 +202,7 @@ public async Task Set_SetsAllTheLayers() public async Task Set_TriggersCacheChangeExtension() { var mockExtension = new Mock(); - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, new[] { mockExtension.Object }); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, new[] { mockExtension.Object }); var cacheEntry = await cacheStack.SetAsync("Set_TriggersCacheChangeExtension", 42, TimeSpan.FromDays(1)); mockExtension.Verify(e => e.OnCacheUpdateAsync("Set_TriggersCacheChangeExtension", cacheEntry.Expiry, CacheUpdateType.AddOrUpdateEntry), Times.Once); @@ -211,19 +211,19 @@ public async Task Set_TriggersCacheChangeExtension() [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task GetOrSet_ThrowsOnNullKey() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetOrSetAsync(null, (old) => Task.FromResult(5), new CacheSettings(TimeSpan.FromDays(1))); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public async Task GetOrSet_ThrowsOnNullGetter() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.GetOrSetAsync("MyCacheKey", null, new CacheSettings(TimeSpan.FromDays(1))); } [TestMethod] public async Task GetOrSet_CacheMiss() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); var result = await cacheStack.GetOrSetAsync("GetOrSet_CacheMiss", (oldValue) => { return Task.FromResult(5); @@ -234,7 +234,7 @@ public async Task GetOrSet_CacheMiss() [TestMethod] public async Task GetOrSet_CacheHit() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.SetAsync("GetOrSet_CacheHit", 17, TimeSpan.FromDays(2)); Internal.DateTimeProvider.UpdateTime(); @@ -249,7 +249,7 @@ public async Task GetOrSet_CacheHit() [TestMethod] public async Task GetOrSet_StaleCacheHit() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); var cacheEntry = new CacheEntry(17, DateTime.UtcNow.AddDays(2)); await cacheStack.SetAsync("GetOrSet_StaleCacheHit", cacheEntry); @@ -277,7 +277,7 @@ public async Task GetOrSet_BackPropagatesToEarlierCacheLayers() var layer2 = new MemoryCacheLayer(); var layer3 = new MemoryCacheLayer(); - await using var cacheStack = new CacheStack(new[] { layer1, layer2, layer3 }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { layer1, layer2, layer3 }, Array.Empty()); var cacheEntry = new CacheEntry(42, TimeSpan.FromDays(1)); await layer2.SetAsync("GetOrSet_BackPropagatesToEarlierCacheLayers", cacheEntry); @@ -299,7 +299,7 @@ public async Task GetOrSet_BackPropagatesToEarlierCacheLayers() [TestMethod] public async Task GetOrSet_ConcurrentStaleCacheHits_OnlyOneRefresh() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); var cacheEntry = new CacheEntry(23, DateTime.UtcNow.AddDays(2)); await cacheStack.SetAsync("GetOrSet_ConcurrentStaleCacheHits_OnlyOneRefresh", cacheEntry); @@ -330,7 +330,7 @@ await cacheStack.GetOrSetAsync( [TestMethod] public async Task GetOrSet_ConcurrentAccess_OnException() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); Internal.DateTimeProvider.UpdateTime(); var getterCallCount = 0; @@ -374,7 +374,7 @@ await cacheStack.GetOrSetAsync( [DataRow(42)] public async Task GetOrSet_ConcurrentAccess_SameResultForAllTasks(int? expectedResult) { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); Internal.DateTimeProvider.UpdateTime(); var getterCallCount = 0; @@ -409,7 +409,7 @@ public async Task GetOrSet_ConcurrentAccess_SameResultForAllTasks(int? expectedR [TestMethod, ExpectedException(typeof(ObjectDisposedException))] public async Task GetOrSet_ThrowsOnUseAfterDisposal() { - var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); await using (cacheStack) { } @@ -418,7 +418,7 @@ public async Task GetOrSet_ThrowsOnUseAfterDisposal() [TestMethod] public async Task GetOrSet_WaitingForRefresh() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, null); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, null); var gettingLockSource = new TaskCompletionSource(); var continueRefreshSource = new TaskCompletionSource(); @@ -454,7 +454,7 @@ public async Task GetOrSet_WaitingForRefresh() [TestMethod] public async Task GetOrSet_ExpiredCacheHit() { - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); await cacheStack.SetAsync("GetOrSet_CacheHit", 17, TimeSpan.FromDays(-1)); var result = await cacheStack.GetOrSetAsync("GetOrSet_CacheHit", (oldValue) => diff --git a/tests/CacheTower.Tests/Extensions/AutoCleanupExtensionTests.cs b/tests/CacheTower.Tests/Extensions/AutoCleanupExtensionTests.cs index a54183f1..16e6f079 100644 --- a/tests/CacheTower.Tests/Extensions/AutoCleanupExtensionTests.cs +++ b/tests/CacheTower.Tests/Extensions/AutoCleanupExtensionTests.cs @@ -23,37 +23,31 @@ public void ThrowForInvalidFrequency() [TestMethod, ExpectedException(typeof(InvalidOperationException))] public async Task ThrowForRegisteringTwoCacheStacks() { - await using (var extension = new AutoCleanupExtension(TimeSpan.FromSeconds(30))) - { - //Will register as part of the CacheStack constructor - await using var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, new[] { extension }); - //Force the second register manually - extension.Register(cacheStack); - } + await using var extension = new AutoCleanupExtension(TimeSpan.FromSeconds(30)); + //Will register as part of the CacheStack constructor + await using var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, new[] { extension }); + //Force the second register manually + extension.Register(cacheStack); } [TestMethod] public async Task RunsBackgroundCleanup() { - await using (var extension = new AutoCleanupExtension(TimeSpan.FromMilliseconds(500))) - { - var cacheStackMock = new Mock(); - extension.Register(cacheStackMock.Object); - await Task.Delay(TimeSpan.FromSeconds(2)); - cacheStackMock.Verify(c => c.CleanupAsync(), Times.AtLeast(2)); - } + await using var extension = new AutoCleanupExtension(TimeSpan.FromMilliseconds(500)); + var cacheStackMock = new Mock(); + extension.Register(cacheStackMock.Object); + await Task.Delay(TimeSpan.FromSeconds(2)); + cacheStackMock.Verify(c => c.CleanupAsync(), Times.AtLeast(2)); } [TestMethod] public async Task BackgroundCleanupObeysCancel() { - await using (var extension = new AutoCleanupExtension(TimeSpan.FromMilliseconds(500), new CancellationToken(true))) - { - var cacheStackMock = new Mock(); - extension.Register(cacheStackMock.Object); - await Task.Delay(TimeSpan.FromSeconds(2)); - cacheStackMock.Verify(c => c.CleanupAsync(), Times.Never); - } + await using var extension = new AutoCleanupExtension(TimeSpan.FromMilliseconds(500), new CancellationToken(true)); + var cacheStackMock = new Mock(); + extension.Register(cacheStackMock.Object); + await Task.Delay(TimeSpan.FromSeconds(2)); + cacheStackMock.Verify(c => c.CleanupAsync(), Times.Never); } } } diff --git a/tests/CacheTower.Tests/Extensions/Redis/RedisRemoteEvictionExtensionTests.cs b/tests/CacheTower.Tests/Extensions/Redis/RedisRemoteEvictionExtensionTests.cs index f2dbfaa8..045455a0 100644 --- a/tests/CacheTower.Tests/Extensions/Redis/RedisRemoteEvictionExtensionTests.cs +++ b/tests/CacheTower.Tests/Extensions/Redis/RedisRemoteEvictionExtensionTests.cs @@ -222,7 +222,7 @@ public async Task NoEvictionOnNewEntries() var cacheLayerOne = new Mock(); var extensionOne = new RedisRemoteEvictionExtension(connectionMock.Object); - var cacheStackOne = new CacheStack(new[] { cacheLayerOne.Object }, new[] { extensionOne }); + var cacheStackOne = new CacheStack(null, new[] { cacheLayerOne.Object }, new[] { extensionOne }); await cacheStackOne.GetOrSetAsync("NoEvictionOnNewEntries", _ => Task.FromResult(1), new CacheSettings(TimeSpan.FromMinutes(5))); diff --git a/tests/CacheTower.Tests/FuncCacheContextActivator.cs b/tests/CacheTower.Tests/FuncCacheContextActivator.cs new file mode 100644 index 00000000..ad4aa920 --- /dev/null +++ b/tests/CacheTower.Tests/FuncCacheContextActivator.cs @@ -0,0 +1,37 @@ +using System; + +namespace CacheTower.Tests; + +internal class FuncCacheContextActivator : ICacheContextActivator +{ + private readonly Func Resolver; + + public FuncCacheContextActivator(Func resolver) + { + Resolver = resolver; + } + + public ICacheContextScope BeginScope() + { + return new FuncCacheContextScope(Resolver); + } +} + +internal class FuncCacheContextScope : ICacheContextScope +{ + private readonly Func Resolver; + + public FuncCacheContextScope(Func resolver) + { + Resolver = resolver; + } + + public object Resolve(Type type) + { + return Resolver()!; + } + + public void Dispose() + { + } +} From 7f7f34dc976db5cae36440955fc4cc66dac01266 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 22:53:53 +0930 Subject: [PATCH 6/8] Fix build errors in benchmarks --- .../CacheAlternatives_File_Benchmark.cs | 6 ++--- .../CacheAlternatives_Memory_Benchmark.cs | 2 +- ...eAlternatives_Memory_Parallel_Benchmark.cs | 2 +- .../CacheAlternatives_Redis_Benchmark.cs | 2 +- ...heAlternatives_Redis_Parallel_Benchmark.cs | 2 +- .../CacheStackBenchmark.cs | 22 +++++++++---------- .../Extensions/BaseExtensionsBenchmark.cs | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_File_Benchmark.cs b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_File_Benchmark.cs index 8cf58dde..78f5c24c 100644 --- a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_File_Benchmark.cs +++ b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_File_Benchmark.cs @@ -23,9 +23,9 @@ public class CacheAlternatives_File_Benchmark : BaseBenchmark public CacheAlternatives_File_Benchmark() { - CacheTowerNewtonsoftJson = new CacheStack(new[] { new FileCacheLayer(new(DirectoryPath, NewtonsoftJsonCacheSerializer.Instance)) }, Array.Empty()); - CacheTowerSystemTextJson = new CacheStack(new[] { new FileCacheLayer(new(DirectoryPath, SystemTextJsonCacheSerializer.Instance)) }, Array.Empty()); - CacheTowerProtobuf = new CacheStack(new[] { new FileCacheLayer(new(DirectoryPath, ProtobufCacheSerializer.Instance)) }, Array.Empty()); + CacheTowerNewtonsoftJson = new CacheStack(null, new[] { new FileCacheLayer(new(DirectoryPath, NewtonsoftJsonCacheSerializer.Instance)) }, Array.Empty()); + CacheTowerSystemTextJson = new CacheStack(null, new[] { new FileCacheLayer(new(DirectoryPath, SystemTextJsonCacheSerializer.Instance)) }, Array.Empty()); + CacheTowerProtobuf = new CacheStack(null, new[] { new FileCacheLayer(new(DirectoryPath, ProtobufCacheSerializer.Instance)) }, Array.Empty()); } private static void CleanupFileSystem() diff --git a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Benchmark.cs b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Benchmark.cs index 8c2848f7..896a7b9e 100644 --- a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Benchmark.cs +++ b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Benchmark.cs @@ -21,7 +21,7 @@ public class CacheAlternatives_Memory_Benchmark : BaseBenchmark public CacheAlternatives_Memory_Benchmark() { - CacheTower = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + CacheTower = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); CacheManager = CacheFactory.Build(b => { b.WithMicrosoftMemoryCacheHandle(); diff --git a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Parallel_Benchmark.cs b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Parallel_Benchmark.cs index fa36f3b7..92a6ece3 100644 --- a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Parallel_Benchmark.cs +++ b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Memory_Parallel_Benchmark.cs @@ -23,7 +23,7 @@ public class CacheAlternatives_Memory_Parallel_Benchmark : BaseBenchmark public CacheAlternatives_Memory_Parallel_Benchmark() { - CacheTower = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + CacheTower = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); CacheManager = CacheFactory.Build(b => { b.WithMicrosoftMemoryCacheHandle(); diff --git a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Benchmark.cs b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Benchmark.cs index 3f1efb2e..5b0cc839 100644 --- a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Benchmark.cs +++ b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Benchmark.cs @@ -21,7 +21,7 @@ public class CacheAlternatives_Redis_Benchmark : BaseBenchmark public CacheAlternatives_Redis_Benchmark() { - CacheTower = new CacheStack(new[] { new RedisCacheLayer(RedisHelper.GetConnection(), new RedisCacheLayerOptions(ProtobufCacheSerializer.Instance)) }, Array.Empty()); + CacheTower = new CacheStack(null, new[] { new RedisCacheLayer(RedisHelper.GetConnection(), new RedisCacheLayerOptions(ProtobufCacheSerializer.Instance)) }, Array.Empty()); CacheManager = CacheFactory.Build(b => { b.WithRedisConfiguration("redisLocal", "localhost:6379,ssl=false"); diff --git a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Parallel_Benchmark.cs b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Parallel_Benchmark.cs index 9b762b79..6475d448 100644 --- a/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Parallel_Benchmark.cs +++ b/benchmarks/CacheTower.AlternativesBenchmark/CacheAlternatives_Redis_Parallel_Benchmark.cs @@ -24,7 +24,7 @@ public class CacheAlternatives_Redis_Parallel_Benchmark : BaseBenchmark public CacheAlternatives_Redis_Parallel_Benchmark() { - CacheTower = new CacheStack(new[] { new RedisCacheLayer(RedisHelper.GetConnection(), new RedisCacheLayerOptions(ProtobufCacheSerializer.Instance)) }, Array.Empty()); + CacheTower = new CacheStack(null, new[] { new RedisCacheLayer(RedisHelper.GetConnection(), new RedisCacheLayerOptions(ProtobufCacheSerializer.Instance)) }, Array.Empty()); CacheManager = CacheFactory.Build(b => { b.WithRedisConfiguration("redisLocal", "localhost:6379,ssl=false"); diff --git a/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs b/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs index 8c964314..f5568717 100644 --- a/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs +++ b/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs @@ -34,7 +34,7 @@ public ConfigSettings() [Benchmark] public async Task Set() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -45,7 +45,7 @@ public async Task Set() [Benchmark] public async Task Set_TwoLayers() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -56,7 +56,7 @@ public async Task Set_TwoLayers() [Benchmark] public async Task Evict() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -68,7 +68,7 @@ public async Task Evict() [Benchmark] public async Task Evict_TwoLayers() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -80,7 +80,7 @@ public async Task Evict_TwoLayers() [Benchmark] public async Task Cleanup() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -92,7 +92,7 @@ public async Task Cleanup() [Benchmark] public async Task Cleanup_TwoLayers() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -104,7 +104,7 @@ public async Task Cleanup_TwoLayers() [Benchmark] public async Task GetMiss() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -115,7 +115,7 @@ public async Task GetMiss() [Benchmark] public async Task GetHit() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { await cacheStack.SetAsync("GetHit", 15, TimeSpan.FromDays(1)); @@ -128,7 +128,7 @@ public async Task GetHit() [Benchmark] public async Task GetOrSet_NeverStale() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -142,7 +142,7 @@ await cacheStack.GetOrSetAsync("GetOrSet", (old) => [Benchmark] public async Task GetOrSet_AlwaysStale() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { for (var i = 0; i < WorkIterations; i++) { @@ -156,7 +156,7 @@ await cacheStack.GetOrSetAsync("GetOrSet", (old) => [Benchmark] public async Task GetOrSet_UnderLoad() { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + await using (var cacheStack = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty())) { await cacheStack.SetAsync("GetOrSet", new CacheEntry(15, DateTime.UtcNow.AddDays(-1))); diff --git a/benchmarks/CacheTower.Benchmarks/Extensions/BaseExtensionsBenchmark.cs b/benchmarks/CacheTower.Benchmarks/Extensions/BaseExtensionsBenchmark.cs index e1e9ae29..16b39dc2 100644 --- a/benchmarks/CacheTower.Benchmarks/Extensions/BaseExtensionsBenchmark.cs +++ b/benchmarks/CacheTower.Benchmarks/Extensions/BaseExtensionsBenchmark.cs @@ -30,7 +30,7 @@ public ConfigSettings() protected virtual void SetupBenchmark() { } protected virtual void CleanupBenchmark() { } - protected static CacheStack CacheStack { get; } = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty()); + protected static CacheStack CacheStack { get; } = new CacheStack(null, new[] { new MemoryCacheLayer() }, Array.Empty()); [GlobalSetup] public void Setup() From e39b5e5fc3ffa6caf3fce85760904c28ee9c364f Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 23:20:36 +0930 Subject: [PATCH 7/8] Fix service collection tests --- .../ServiceCollectionExtensionsTests.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/CacheTower.Tests/ServiceCollectionExtensionsTests.cs b/tests/CacheTower.Tests/ServiceCollectionExtensionsTests.cs index be8757f8..af22d64c 100644 --- a/tests/CacheTower.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/CacheTower.Tests/ServiceCollectionExtensionsTests.cs @@ -42,10 +42,9 @@ public void CacheStack_CacheStackBuilder() hasBuilderBeenCalled = true; builder.AddMemoryCacheLayer(); }); + var serviceProvider = serviceCollection.BuildServiceProvider(); - var serviceDescriptor = serviceCollection[0]; - Assert.AreEqual(ServiceLifetime.Singleton, serviceDescriptor.Lifetime); - var result = (IExtendableCacheStack)serviceDescriptor.ImplementationFactory(null); + var result = (IExtendableCacheStack)serviceProvider.GetRequiredService(); var cacheLayers = result.GetCacheLayers(); Assert.AreEqual(1, cacheLayers.Count); Assert.AreEqual(typeof(MemoryCacheLayer), cacheLayers[0].GetType()); @@ -125,10 +124,9 @@ public void GenericCacheStack_CacheStackBuilder() hasBuilderBeenCalled = true; builder.AddMemoryCacheLayer(); }); + var serviceProvider = serviceCollection.BuildServiceProvider(); - var serviceDescriptor = serviceCollection[0]; - Assert.AreEqual(ServiceLifetime.Singleton, serviceDescriptor.Lifetime); - var result = (IExtendableCacheStack)serviceDescriptor.ImplementationFactory(null); + var result = (IExtendableCacheStack)serviceProvider.GetRequiredService>(); var cacheLayers = result.GetCacheLayers(); Assert.AreEqual(1, cacheLayers.Count); Assert.AreEqual(typeof(MemoryCacheLayer), cacheLayers[0].GetType()); @@ -188,10 +186,9 @@ public void GenericCacheStack_CacheStackBuilder_CustomCacheContextActivator() hasBuilderBeenCalled = true; builder.AddMemoryCacheLayer(); }); + var serviceProvider = serviceCollection.BuildServiceProvider(); - var serviceDescriptor = serviceCollection[0]; - Assert.AreEqual(ServiceLifetime.Singleton, serviceDescriptor.Lifetime); - var result = (IExtendableCacheStack)serviceDescriptor.ImplementationFactory(null); + var result = (IExtendableCacheStack)serviceProvider.GetRequiredService>(); var cacheLayers = result.GetCacheLayers(); Assert.AreEqual(1, cacheLayers.Count); Assert.AreEqual(typeof(MemoryCacheLayer), cacheLayers[0].GetType()); From d28fed97c984bba7aa5f8c3a244f8c64afdd50d9 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 8 Apr 2023 23:39:17 +0930 Subject: [PATCH 8/8] Allow init properties for CacheEntry Makes Protobuf happy and is probably a better solution than making Protobuf skip any constructors --- src/CacheTower/CacheEntry.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/CacheTower/CacheEntry.cs b/src/CacheTower/CacheEntry.cs index 3828c463..3a7aa17b 100644 --- a/src/CacheTower/CacheEntry.cs +++ b/src/CacheTower/CacheEntry.cs @@ -66,15 +66,20 @@ public sealed record CacheEntry(T? Value, DateTime Expiry) : ICacheEntry /// /// The cached value. /// - public T? Value { get; } = Value; + public T? Value { get; init; } = Value; /// /// The expiry date for the cache entry. /// - public DateTime Expiry { get; } = new DateTime( + public DateTime Expiry { get; init; } = new DateTime( Expiry.Year, Expiry.Month, Expiry.Day, Expiry.Hour, Expiry.Minute, Expiry.Second, DateTimeKind.Utc ); + /// + /// Creates a new with a default value. + /// + public CacheEntry() : this(default, DateTime.MinValue) { } + /// /// Creates a new with the given and an expiry adjusted to the . ///