From 35e9ceaeef68a85296d8b3669cea0fc1d5e09385 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 10 Apr 2024 19:21:32 +0100 Subject: [PATCH 01/33] initial API cut (post review) --- AspNetCore.sln | 41 ++++++ eng/ProjectReferences.props | 1 + eng/SharedFramework.Local.props | 1 + eng/ShippingAssemblies.props | 1 + eng/TrimmableProjects.props | 1 + src/Caching/Caching.slnf | 2 + .../src/HybridCacheBuilderExtensions.cs | 64 +++++++++ src/Caching/Hybrid/src/HybridCacheOptions.cs | 45 +++++++ .../src/HybridCacheServiceExtensions.cs | 64 +++++++++ src/Caching/Hybrid/src/IHybridCacheBuilder.cs | 27 ++++ .../Hybrid/src/Internal/DefaultHybridCache.cs | 23 ++++ .../Internal/DefaultJsonSerializerFactory.cs | 37 ++++++ .../src/Internal/InbuiltTypeSerializer.cs | 54 ++++++++ ...Microsoft.Extensions.Caching.Hybrid.csproj | 26 ++++ src/Caching/Hybrid/src/PublicAPI.Shipped.txt | 1 + .../Hybrid/src/PublicAPI.Unshipped.txt | 60 +++++++++ src/Caching/Hybrid/src/Runtime/HybridCache.cs | 125 ++++++++++++++++++ .../src/Runtime/HybridCacheEntryFlags.cs | 50 +++++++ .../src/Runtime/HybridCacheEntryOptions.cs | 32 +++++ .../src/Runtime/IBufferDistributedCache.cs | 50 +++++++ .../src/Runtime/IHybridCacheSerializer.cs | 24 ++++ .../Runtime/IHybridCacheSerializerFactory.cs | 20 +++ .../Hybrid/src/Runtime/IsExternalInit.cs | 11 ++ src/Caching/Hybrid/src/Runtime/readme.md | 2 + ...oft.Extensions.Caching.Hybrid.Tests.csproj | 13 ++ 25 files changed, 775 insertions(+) create mode 100644 src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs create mode 100644 src/Caching/Hybrid/src/HybridCacheOptions.cs create mode 100644 src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs create mode 100644 src/Caching/Hybrid/src/IHybridCacheBuilder.cs create mode 100644 src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs create mode 100644 src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs create mode 100644 src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs create mode 100644 src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj create mode 100644 src/Caching/Hybrid/src/PublicAPI.Shipped.txt create mode 100644 src/Caching/Hybrid/src/PublicAPI.Unshipped.txt create mode 100644 src/Caching/Hybrid/src/Runtime/HybridCache.cs create mode 100644 src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs create mode 100644 src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs create mode 100644 src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs create mode 100644 src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs create mode 100644 src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs create mode 100644 src/Caching/Hybrid/src/Runtime/IsExternalInit.cs create mode 100644 src/Caching/Hybrid/src/Runtime/readme.md create mode 100644 src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj diff --git a/AspNetCore.sln b/AspNetCore.sln index 68269bb213ca..8547083d378e 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1788,6 +1788,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotReferencedInWasmCodePack EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components.WasmRemoteAuthentication", "src\Components\test\testassets\Components.WasmRemoteAuthentication\Components.WasmRemoteAuthentication.csproj", "{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hybrid", "Hybrid", "{2D64CA23-6E81-488E-A7D3-9BDF87240098}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Caching.Hybrid", "src\Caching\Hybrid\src\Microsoft.Extensions.Caching.Hybrid.csproj", "{2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Caching.Hybrid.Tests", "src\Caching\Hybrid\test\Microsoft.Extensions.Caching.Hybrid.Tests.csproj", "{CF63C942-895A-4F6B-888A-7653D7C4991A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -10789,6 +10795,38 @@ Global {8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x64.Build.0 = Release|Any CPU {8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x86.ActiveCfg = Release|Any CPU {8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x86.Build.0 = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|arm64.ActiveCfg = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|arm64.Build.0 = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|x64.Build.0 = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Debug|x86.Build.0 = Debug|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|Any CPU.Build.0 = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|arm64.ActiveCfg = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|arm64.Build.0 = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|x64.ActiveCfg = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|x64.Build.0 = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|x86.ActiveCfg = Release|Any CPU + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9}.Release|x86.Build.0 = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|arm64.ActiveCfg = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|arm64.Build.0 = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|x64.ActiveCfg = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|x64.Build.0 = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Debug|x86.Build.0 = Debug|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|Any CPU.Build.0 = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|arm64.ActiveCfg = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|arm64.Build.0 = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|x64.ActiveCfg = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|x64.Build.0 = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|x86.ActiveCfg = Release|Any CPU + {CF63C942-895A-4F6B-888A-7653D7C4991A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -11672,6 +11710,9 @@ Global {15D08EA7-8C63-45FB-8B4D-C5F8E43B433E} = {05A169C7-4F20-4516-B10A-B13C5649D346} {433F91E4-E39D-4EB0-B798-2998B3969A2C} = {6126DCE4-9692-4EE2-B240-C65743572995} {8A021D6D-7935-4AB3-BB47-38D4FF9B0D13} = {6126DCE4-9692-4EE2-B240-C65743572995} + {2D64CA23-6E81-488E-A7D3-9BDF87240098} = {0F39820F-F4A5-41C6-9809-D79B68F032EF} + {2B60E6D3-9E7C-427A-AD4E-BBE9A6D935B9} = {2D64CA23-6E81-488E-A7D3-9BDF87240098} + {CF63C942-895A-4F6B-888A-7653D7C4991A} = {2D64CA23-6E81-488E-A7D3-9BDF87240098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F} diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 7686ce1e869c..caac54022a4d 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -5,6 +5,7 @@ --> + diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props index 46be57d9577b..ea4d7df14844 100644 --- a/eng/SharedFramework.Local.props +++ b/eng/SharedFramework.Local.props @@ -8,6 +8,7 @@ + diff --git a/eng/ShippingAssemblies.props b/eng/ShippingAssemblies.props index bd06923b2454..9a84ba0198ba 100644 --- a/eng/ShippingAssemblies.props +++ b/eng/ShippingAssemblies.props @@ -5,6 +5,7 @@ --> + diff --git a/eng/TrimmableProjects.props b/eng/TrimmableProjects.props index c61ab1b2e4ac..e4ec572527d4 100644 --- a/eng/TrimmableProjects.props +++ b/eng/TrimmableProjects.props @@ -7,6 +7,7 @@ --> + diff --git a/src/Caching/Caching.slnf b/src/Caching/Caching.slnf index dcecdb8a91c7..63610b8e28d5 100644 --- a/src/Caching/Caching.slnf +++ b/src/Caching/Caching.slnf @@ -2,6 +2,8 @@ "solution": { "path": "..\\..\\AspNetCore.sln", "projects": [ + "src\\Caching\\Hybrid\\src\\Microsoft.Extensions.Caching.Hybrid.csproj", + "src\\Caching\\Hybrid\\test\\Microsoft.Extensions.Caching.Hybrid.Tests.csproj", "src\\Caching\\SqlServer\\src\\Microsoft.Extensions.Caching.SqlServer.csproj", "src\\Caching\\SqlServer\\test\\Microsoft.Extensions.Caching.SqlServer.Tests.csproj", "src\\Caching\\StackExchangeRedis\\src\\Microsoft.Extensions.Caching.StackExchangeRedis.csproj", diff --git a/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs b/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs new file mode 100644 index 000000000000..3ce69ead2c35 --- /dev/null +++ b/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Configuration extension methods for / +/// +public static class HybridCacheBuilderExtensions +{ + /// + /// Serialize values of type with the specified serializer from + /// + public static IHybridCacheBuilder WithSerializer(this IHybridCacheBuilder builder, IHybridCacheSerializer serializer) + { + builder.Services.AddSingleton>(serializer); + return builder; + } + + /// + /// Serialize values of type with the serializer of type + /// + public static IHybridCacheBuilder WithSerializer(this IHybridCacheBuilder builder) + where TImplementation : class, IHybridCacheSerializer + { + builder.Services.AddSingleton, TImplementation>(); + return builder; + } + + /// + /// Add as an additional serializer factory, which can provide serializers for multiple types + /// + public static IHybridCacheBuilder WithSerializerFactory(this IHybridCacheBuilder builder, IHybridCacheSerializerFactory factory) + { + builder.Services.AddSingleton(factory); + return builder; + } + + /// + /// Add a factory of type as an additional serializer factory, which can provide serializers for multiple types + /// + public static IHybridCacheBuilder WithSerializerFactory< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + TImplementation>(this IHybridCacheBuilder builder) + where TImplementation : class, IHybridCacheSerializerFactory + { + builder.Services.AddSingleton(); + return builder; + } +} diff --git a/src/Caching/Hybrid/src/HybridCacheOptions.cs b/src/Caching/Hybrid/src/HybridCacheOptions.cs new file mode 100644 index 000000000000..daca158a4504 --- /dev/null +++ b/src/Caching/Hybrid/src/HybridCacheOptions.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Options for configuring the default implementation +/// +public class HybridCacheOptions +{ + /// + /// Default global options to be applied to operations; if options are + /// specified at the individual call level, the non-null values are merged (with the per-call + /// options being used in preference to the global options). If no value is specified for a given + /// option (globally or per-call), the implementation may choose a reasonable default. + /// + public HybridCacheEntryOptions? DefaultOptions { get; set; } + + /// + /// Disallow compression for this instance + /// + public bool DisableCompression { get; set; } + + /// + /// The maximum size of cache items; attempts to store values over this size will be logged. + /// + public long MaximumPayloadBytes { get; set; } = 1 << 20; // 1MiB + + /// + /// The maximum permitted length (in characters) of keys; attempts to use keys over this size will be logged. + /// + public int MaximumKeyLength { get; set; } = 1024; // characters + + /// + /// Use "tags" data as dimensions on metric reporting; if enabled, care should be used to ensure that + /// tags do not contain data that should not be visible in metrics systems. + /// + public bool ReportTagMetrics { get; set; } +} diff --git a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs new file mode 100644 index 000000000000..26bf37fc1495 --- /dev/null +++ b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Hybrid.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Internal; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Configuration extension methods for +/// +public static class HybridCacheServiceExtensions +{ + /// + /// Adds support for multi-tier caching services + /// + /// A builder instance that allows further configuration of the system + public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services, Action setupAction) + { +#if NET7_0_OR_GREATER + ArgumentNullException.ThrowIfNull(setupAction); +#else + _ = setupAction ?? throw new ArgumentNullException(nameof(setupAction)); +#endif + AddHybridCache(services); + services.Configure(setupAction); + return new HybridCacheBuilder(services); + } + + /// + /// Adds support for multi-tier caching services + /// + /// A builder instance that allows further configuration of the system + public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services) + { +#if NET7_0_OR_GREATER + ArgumentNullException.ThrowIfNull(services); +#else + _ = services ?? throw new ArgumentNullException(nameof(services)); +#endif + +#if NET8_0_OR_GREATER + services.TryAddSingleton(TimeProvider.System); +#else + services.TryAddSingleton(); +#endif + services.AddOptions(); + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); // we need a backend; use in-proc by default + services.AddSingleton(); + services.AddSingleton>(InbuiltTypeSerializer.Instance); + services.AddSingleton>(InbuiltTypeSerializer.Instance); + services.AddSingleton(); + return new HybridCacheBuilder(services); + } +} diff --git a/src/Caching/Hybrid/src/IHybridCacheBuilder.cs b/src/Caching/Hybrid/src/IHybridCacheBuilder.cs new file mode 100644 index 000000000000..fae49c030fc3 --- /dev/null +++ b/src/Caching/Hybrid/src/IHybridCacheBuilder.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Helper API for configuring . +/// +public interface IHybridCacheBuilder +{ + /// + /// Gets the services collection associated with this instance. + /// + IServiceCollection Services { get; } +} + +internal sealed class HybridCacheBuilder(IServiceCollection services) : IHybridCacheBuilder +{ + public IServiceCollection Services { get; } = services; +} diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs new file mode 100644 index 000000000000..dfad1bac3cd8 --- /dev/null +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Caching.Hybrid.Internal; +internal sealed class DefaultHybridCache : HybridCache +{ + public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) + => underlyingDataCallback(state, token); // pass-thru without caching for initial API pass + + public override ValueTask RemoveKeyAsync(string key, CancellationToken token = default) + => default; // no cache, nothing to remove + + public override ValueTask RemoveTagAsync(string tag, CancellationToken token = default) + => default; // no cache, nothing to remove + + public override ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) + => default; // no cache, nothing to set +} diff --git a/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs b/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs new file mode 100644 index 000000000000..dbe94684d185 --- /dev/null +++ b/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace Microsoft.Extensions.Caching.Hybrid.Internal; +internal sealed class DefaultJsonSerializerFactory : IHybridCacheSerializerFactory +{ + public bool TryCreateSerializer([NotNullWhen(true)] out IHybridCacheSerializer? serializer) + { + // no restriction + serializer = new DefaultJsonSerializer(); + return true; + } + + internal sealed class DefaultJsonSerializer : IHybridCacheSerializer + { + T IHybridCacheSerializer.Deserialize(ReadOnlySequence source) + { + var reader = new Utf8JsonReader(source); +#pragma warning disable IL2026, IL3050 // AOT bits + return JsonSerializer.Deserialize(ref reader)!; +#pragma warning restore IL2026, IL3050 + } + + void IHybridCacheSerializer.Serialize(T value, IBufferWriter target) + { + using var writer = new Utf8JsonWriter(target); +#pragma warning disable IL2026, IL3050 // AOT bits + JsonSerializer.Serialize(writer, value, JsonSerializerOptions.Default); +#pragma warning restore IL2026, IL3050 + } + } + +} diff --git a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs new file mode 100644 index 000000000000..6160a2e2ae55 --- /dev/null +++ b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Extensions.Caching.Hybrid.Internal; +internal sealed class InbuiltTypeSerializer : IHybridCacheSerializer, IHybridCacheSerializer +{ + private static InbuiltTypeSerializer? _instance; + public static InbuiltTypeSerializer Instance => _instance ??= new(); + string IHybridCacheSerializer.Deserialize(ReadOnlySequence source) + { +#if NET5_0_OR_GREATER + return Encoding.UTF8.GetString(source); +#else + if (source.IsSingleSegment && MemoryMarshal.TryGetArray(source.First, out var segment)) + { + // we can use the existing single chunk as-is + return Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count); + } + + var length = checked((int)source.Length); + var oversized = ArrayPool.Shared.Rent(length); + source.CopyTo(oversized); + var s = Encoding.UTF8.GetString(oversized, 0, length); + ArrayPool.Shared.Return(oversized); + return s; +#endif + } + + void IHybridCacheSerializer.Serialize(string value, IBufferWriter target) + { +#if NET5_0_OR_GREATER + Encoding.UTF8.GetBytes(value, target); +#else + var length = Encoding.UTF8.GetByteCount(value); + var oversized = ArrayPool.Shared.Rent(length); + var actual = Encoding.UTF8.GetBytes(value, 0, value.Length, oversized, 0); + Debug.Assert(actual == length); + target.Write(new(oversized, 0, length)); +#endif + } + + byte[] IHybridCacheSerializer.Deserialize(ReadOnlySequence source) + => source.ToArray(); + + void IHybridCacheSerializer.Serialize(byte[] value, IBufferWriter target) + => target.Write(value); +} diff --git a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj new file mode 100644 index 000000000000..54f94640dd73 --- /dev/null +++ b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj @@ -0,0 +1,26 @@ + + + + Multi-level caching implementation building on and extending IDistributedCache + $(DefaultNetCoreTargetFramework);$(DefaultNetFxTargetFramework);netstandard2.0 + true + cache;distributedcache;hybrid + true + true + true + true + true + + + + + + + + + + + + + + diff --git a/src/Caching/Hybrid/src/PublicAPI.Shipped.txt b/src/Caching/Hybrid/src/PublicAPI.Shipped.txt new file mode 100644 index 000000000000..ab058de62d44 --- /dev/null +++ b/src/Caching/Hybrid/src/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt new file mode 100644 index 000000000000..b7195e28c677 --- /dev/null +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -0,0 +1,60 @@ +#nullable enable +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.ICollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveKeyAsync(string! key, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveTagAsync(string! tag, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.SetAsync(string! key, T value, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.ICollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache +Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.Set(string! key, System.Buffers.ReadOnlySequence value, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions! options) -> void +Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.SetAsync(string! key, System.Buffers.ReadOnlySequence value, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions! options, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGet(string! key, System.Buffers.IBufferWriter! destination) -> bool +Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGetAsync(string! key, System.Buffers.IBufferWriter! destination, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Microsoft.Extensions.Caching.Hybrid.HybridCache +Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.ICollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Microsoft.Extensions.Caching.Hybrid.HybridCache.HybridCache() -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableCompression = 32 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableDistributedCache = Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableDistributedCacheRead | Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableDistributedCacheWrite -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableDistributedCacheRead = 4 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableDistributedCacheWrite = 8 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCache = Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheRead | Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheWrite -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheRead = 1 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheWrite = 2 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableUndelyingData = 16 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.None = 0 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.Expiration.get -> System.TimeSpan? +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.Expiration.init -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.Flags.get -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags? +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.Flags.init -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.HybridCacheEntryOptions() -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.LocalCacheExpiration.get -> System.TimeSpan? +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.LocalCacheExpiration.init -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DefaultOptions.get -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DefaultOptions.set -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DisableCompression.get -> bool +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DisableCompression.set -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.HybridCacheOptions() -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.MaximumKeyLength.get -> int +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.MaximumKeyLength.set -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.MaximumPayloadBytes.get -> long +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.MaximumPayloadBytes.set -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.ReportTagMetrics.get -> bool +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.ReportTagMetrics.set -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheServiceExtensions +Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder +Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializer +Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializer.Deserialize(System.Buffers.ReadOnlySequence source) -> T +Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializer.Serialize(T value, System.Buffers.IBufferWriter! target) -> void +Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializerFactory +Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializerFactory.TryCreateSerializer(out Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializer? serializer) -> bool +static Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions.WithSerializer(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! +static Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions.WithSerializer(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder, Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializer! serializer) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! +static Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions.WithSerializerFactory(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder, Microsoft.Extensions.Caching.Hybrid.IHybridCacheSerializerFactory! factory) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! +static Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions.WithSerializerFactory(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! +static Microsoft.Extensions.Caching.Hybrid.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! +static Microsoft.Extensions.Caching.Hybrid.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! setupAction) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! +virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveKeysAsync(System.Collections.Generic.ICollection! keys, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveTagsAsync(System.Collections.Generic.ICollection! tags, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs new file mode 100644 index 000000000000..f9f27808a7d3 --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Provides multi-tier caching services building on backends. +/// +public abstract class HybridCache +{ + /// + /// Get data from the cache, or the underlying data service if not available. + /// + /// The type of the data being considered + /// The type of additional state required by + /// The unique key for this cache entry + /// Provides the underlying data service is the data is not available in the cache + /// Additional state required for + /// Additional options for this cache entry + /// The tags to associate with this cache item + /// Cancellation for this operation + /// The data, either from cache or the underlying data service + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] + public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, + HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default); + + /// + /// Get data from the cache, or the underlying data service if not available. + /// + /// The type of the data being considered + /// The unique key for this cache entry + /// Provides the underlying data service is the data is not available in the cache + /// Additional options for this cache entry + /// The tags to associate with this cache item + /// Cancellation for this operation + /// The data, either from cache or the underlying data service + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] + public ValueTask GetOrCreateAsync(string key, Func> underlyingDataCallback, + HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) + => GetOrCreateAsync(key, underlyingDataCallback, WrappedCallbackCache.Instance, options, tags, token); + + private static class WrappedCallbackCache // per-T memoized helper that allows GetOrCreateAsync and GetOrCreateAsync to share an implementation + { + // for the simple usage scenario (no TState), pack the original callback as the "state", and use a wrapper function that just unrolls and invokes from the state + public static readonly Func>, CancellationToken, ValueTask> Instance = static (callback, ct) => callback(ct); + } + + /// + /// Manually insert or overwrite a cache entry. + /// + /// The type of the data being considered + /// The unique key for this cache entry + /// The value to assign for this cache item + /// Additional options for this cache entry + /// The tags to associate with this cache item + /// Cancellation for this operation + public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default); + + /// + /// Removes cache data with the specified key + /// + public abstract ValueTask RemoveKeyAsync(string key, CancellationToken token = default); + + /// + /// Removes cache data with the specified keys + /// + public virtual ValueTask RemoveKeysAsync(ICollection keys, CancellationToken token = default) + { + if (keys is null) + { + return default; // for consistency with GetOrCreate/Set: interpret as "none" + } + return keys.Count switch + { + 0 => default, // nothing to do + 1 => RemoveKeyAsync(keys.Single(), token), + _ => Walk(this, keys, token), + }; + + // default implementation is to call RemoveKeyAsync for each key in turn + static async ValueTask Walk(HybridCache @this, IEnumerable keys, CancellationToken token) + { + foreach (var key in keys) + { + await @this.RemoveKeyAsync(key, token).ConfigureAwait(false); + } + } + } + + /// + /// Removes cache data associated with the specified tags + /// + public virtual ValueTask RemoveTagsAsync(ICollection tags, CancellationToken token = default) + { + if (tags is null) + { + return default; // for consistency with GetOrCreate/Set: interpret as "none" + } + return tags.Count switch + { + 0 => default, // nothing to do + 1 => RemoveTagAsync(tags.Single(), token), + _ => Walk(this, tags, token), + }; + + // default implementation is to call RemoveTagAsync for each key in turn + static async ValueTask Walk(HybridCache @this, IEnumerable keys, CancellationToken token) + { + foreach (var key in keys) + { + await @this.RemoveTagAsync(key, token).ConfigureAwait(false); + } + } + } + + /// + /// Removes cache data associated with the specified tag + /// + public abstract ValueTask RemoveTagAsync(string tag, CancellationToken token = default); +} diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs new file mode 100644 index 000000000000..d2521233a72c --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Additional flags that apply to a operation +/// +[Flags] +public enum HybridCacheEntryFlags +{ + /// + /// No additional flags + /// + None = 0, + /// + /// Do not read from the local in-process cache + /// + DisableLocalCacheRead = 1 << 0, + /// + /// Do not write to the local in-process cache + /// + DisableLocalCacheWrite = 1 << 1, + /// + /// Do not use the local in-process cache for reads or writes + /// + DisableLocalCache = DisableLocalCacheRead | DisableLocalCacheWrite, + /// + /// Do not read from the secondary distributed cache + /// + DisableDistributedCacheRead = 1 << 2, + /// + /// Do not write to the secondary distributed cache + /// + DisableDistributedCacheWrite = 1 << 3, + /// + /// Do not use the local in-process cache for reads or writes + /// + DisableDistributedCache = DisableDistributedCacheRead | DisableDistributedCacheWrite, + /// + /// Only fetch the value from cache - do not attempt to access the underlying data store + /// + DisableUndelyingData = 1 << 4, + /// + /// Do not compress this payload + /// + DisableCompression = 1 << 5, +} diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs new file mode 100644 index 000000000000..2d4c1e660f41 --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Additional options (expiration, etc) that apply to a operation. When options +/// can be specified at miltiple levels (for example globally and per-call), the values are composed; the +/// most granular non-null value is used, with null values being inherited. If no value is specified at +/// any level, the implementation may choose a reasonable default. +/// +public sealed class HybridCacheEntryOptions +{ + /// + /// Overall cache duration of this entry, passed to the backend distributed cache + /// + public TimeSpan? Expiration { get; init; } // overall cache duration + + /// + /// Cache duration in local cache; when retrieving a cached value + /// from an external cache store, this value will be used to calculate the local + /// cache expiration, not exceeding the remaining overall cache lifetime + /// + public TimeSpan? LocalCacheExpiration { get; init; } // TTL in L1 + + /// + /// Additional flags that apply to this usage + /// + public HybridCacheEntryFlags? Flags { get; init; } +} diff --git a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs new file mode 100644 index 000000000000..887bef0802f6 --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Caching.Distributed; // intentional for parity with IDistributedCache + +/// +/// Represents a distributed cache of serialized values, with support for low allocation data transfer. +/// +public interface IBufferDistributedCache : IDistributedCache +{ + /// + /// Attempt to retrieve an existing cache item. + /// + /// The unique key for the cache item. + /// Target to write the cache contents on success. + /// True if the cache item is found, False otherwise. + /// This is functionally similar to , but avoiding the array allocation. + bool TryGet(string key, IBufferWriter destination); + /// + /// Attempt to asynchronously retrieve an existing cache item. + /// + /// The unique key for the cache item. + /// Target to write the cache contents on success. + /// Cancellation for this operation. + /// True if the cache item is found, False otherwise. + /// This is functionally similar to , but avoiding the array allocation. + ValueTask TryGetAsync(string key, IBufferWriter destination, CancellationToken token = default); + + /// + /// Insert or overwrite a cache item. + /// + /// The unique key for the cache item. + /// The value for this cache item. + /// The cache options for the value. + /// This is functionally similar to , but avoiding the array allocation. + void Set(string key, ReadOnlySequence value, DistributedCacheEntryOptions options); + /// + /// Asynchronously insert or overwrite a cache item. + /// + /// The unique key for the cache item. + /// The value for this cache item. + /// The cache options for the value. + /// Cancellation for this operation. + /// This is functionally similar to , but avoiding the array allocation. + ValueTask SetAsync(string key, ReadOnlySequence value, DistributedCacheEntryOptions options, CancellationToken token = default); +} diff --git a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs new file mode 100644 index 000000000000..a0595936e70e --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Per-type serialization/deserialization support for +/// +/// The type being serialized/deserialized +public interface IHybridCacheSerializer +{ + /// + /// Deserialize a value from the provided + /// + T Deserialize(ReadOnlySequence source); + + /// + /// Serialize , writing to the provided + /// + void Serialize(T value, IBufferWriter target); +} + diff --git a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs new file mode 100644 index 000000000000..98a27c740e35 --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Extensions.Caching.Hybrid; + +/// +/// Factory provider for per-type instances. +/// +public interface IHybridCacheSerializerFactory +{ + /// + /// Request a serializer for the provided type, if possible. + /// + /// The type being serialized/deserialized + /// The serializer + /// True if the factory supports this type, False otherwise + bool TryCreateSerializer([NotNullWhen(true)] out IHybridCacheSerializer? serializer); +} diff --git a/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs b/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs new file mode 100644 index 000000000000..9c1f8e7280e5 --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +#if !NET5_0_OR_GREATER +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit { } // for "init" support on down-level TFMs +#endif diff --git a/src/Caching/Hybrid/src/Runtime/readme.md b/src/Caching/Hybrid/src/Runtime/readme.md new file mode 100644 index 000000000000..1e2289449f0b --- /dev/null +++ b/src/Caching/Hybrid/src/Runtime/readme.md @@ -0,0 +1,2 @@ +These types are intended to be added to be relocated to `Microsoft.Extensions.Caching.Abstractions`; their inclusion +here is a preview placeholder diff --git a/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj new file mode 100644 index 000000000000..924bd11e1153 --- /dev/null +++ b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + From 12b3a0ef7613fca61f6cdd75d1f3b81ce04cdf98 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 14:50:54 +0100 Subject: [PATCH 02/33] basic API test --- .../Hybrid/src/Internal/DefaultHybridCache.cs | 16 ++++++ ...Microsoft.Extensions.Caching.Hybrid.csproj | 2 +- ...oft.Extensions.Caching.Hybrid.Tests.csproj | 2 +- .../Hybrid/test/ServiceConstructionTests.cs | 54 +++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/Caching/Hybrid/test/ServiceConstructionTests.cs diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index dfad1bac3cd8..c5e23199a1a1 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -5,10 +5,26 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.Caching.Hybrid.Internal; + +/// +/// The inbuilt ASP.NET implementation of +/// internal sealed class DefaultHybridCache : HybridCache { + private readonly IDistributedCache backendCache; + private readonly IServiceProvider services; + private readonly HybridCacheOptions options; + public DefaultHybridCache(IOptions options, IDistributedCache backendCache, IServiceProvider services) + { + this.backendCache = backendCache ?? throw new ArgumentNullException(nameof(backendCache)); + this.services = services ?? throw new ArgumentNullException(nameof(services)); + this.options = options.Value; + } + public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) => underlyingDataCallback(state, token); // pass-thru without caching for initial API pass diff --git a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj index 54f94640dd73..c7f87a1b5079 100644 --- a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj +++ b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj @@ -16,11 +16,11 @@ + - diff --git a/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj index 924bd11e1153..8ab5ad30e764 100644 --- a/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj +++ b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + $(DefaultNetCoreTargetFramework);$(DefaultNetFxTargetFramework) enable enable diff --git a/src/Caching/Hybrid/test/ServiceConstructionTests.cs b/src/Caching/Hybrid/test/ServiceConstructionTests.cs new file mode 100644 index 000000000000..c4244c67df29 --- /dev/null +++ b/src/Caching/Hybrid/test/ServiceConstructionTests.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Hybrid.Internal; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + +namespace Microsoft.Extensions.Caching.Hybrid.Tests; +public class ServiceConstructionTests +{ + [Fact] + public void CanCreateService() + { + var services = new ServiceCollection(); + services.AddHybridCache(); + using var provider = services.BuildServiceProvider(); + Assert.IsType(provider.GetService()); + } + + [Fact] + public async Task BasicStatelessUsage() + { + var services = new ServiceCollection(); + services.AddHybridCache(); + using var provider = services.BuildServiceProvider(); + var cache = provider.GetRequiredService(); + + var expected = Guid.NewGuid().ToString(); + var actual = await cache.GetOrCreateAsync(Me(), async _ => expected); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task BasicStatefulUsage() + { + var services = new ServiceCollection(); + services.AddHybridCache(); + using var provider = services.BuildServiceProvider(); + var cache = provider.GetRequiredService(); + + var expected = Guid.NewGuid().ToString(); + var actual = await cache.GetOrCreateAsync(Me(), expected, async (state, _) => state); + Assert.Equal(expected, actual); + } + + private static string Me([CallerMemberName] string caller = "") => caller; +} From c5d28d41fd7c23b91b55c7f3b76bd119113fcde3 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 16:07:27 +0100 Subject: [PATCH 03/33] prove that the API can be configured --- src/Caching/Hybrid/src/HybridCacheOptions.cs | 2 +- .../Hybrid/src/Internal/DefaultHybridCache.cs | 1 + .../Hybrid/src/PublicAPI.Unshipped.txt | 8 +-- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 26 ++++----- src/Caching/Hybrid/test/BasicConfig.json | 12 ++++ ...oft.Extensions.Caching.Hybrid.Tests.csproj | 7 +++ .../Hybrid/test/ServiceConstructionTests.cs | 58 +++++++++++++++++-- 7 files changed, 87 insertions(+), 27 deletions(-) create mode 100644 src/Caching/Hybrid/test/BasicConfig.json diff --git a/src/Caching/Hybrid/src/HybridCacheOptions.cs b/src/Caching/Hybrid/src/HybridCacheOptions.cs index daca158a4504..c6274d3491d9 100644 --- a/src/Caching/Hybrid/src/HybridCacheOptions.cs +++ b/src/Caching/Hybrid/src/HybridCacheOptions.cs @@ -20,7 +20,7 @@ public class HybridCacheOptions /// options being used in preference to the global options). If no value is specified for a given /// option (globally or per-call), the implementation may choose a reasonable default. /// - public HybridCacheEntryOptions? DefaultOptions { get; set; } + public HybridCacheEntryOptions? DefaultEntryOptions { get; set; } /// /// Disallow compression for this instance diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index c5e23199a1a1..a4a3faea64ce 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -24,6 +24,7 @@ public DefaultHybridCache(IOptions options, IDistributedCach this.services = services ?? throw new ArgumentNullException(nameof(services)); this.options = options.Value; } + internal HybridCacheOptions Options => options; public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) => underlyingDataCallback(state, token); // pass-thru without caching for initial API pass diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index b7195e28c677..78901c957cce 100644 --- a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -31,8 +31,8 @@ Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.HybridCacheEntryOpti Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.LocalCacheExpiration.get -> System.TimeSpan? Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.LocalCacheExpiration.init -> void Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions -Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DefaultOptions.get -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? -Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DefaultOptions.set -> void +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DefaultEntryOptions.get -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DefaultEntryOptions.set -> void Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DisableCompression.get -> bool Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.DisableCompression.set -> void Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions.HybridCacheOptions() -> void @@ -56,5 +56,5 @@ static Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions.WithSeri static Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions.WithSerializerFactory(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! static Microsoft.Extensions.Caching.Hybrid.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! static Microsoft.Extensions.Caching.Hybrid.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! setupAction) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! -virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveKeysAsync(System.Collections.Generic.ICollection! keys, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveTagsAsync(System.Collections.Generic.ICollection! tags, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveKeysAsync(System.Collections.Generic.IEnumerable! keys, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveTagsAsync(System.Collections.Generic.IEnumerable! tags, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index f9f27808a7d3..2b71c72401ab 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -69,16 +69,13 @@ private static class WrappedCallbackCache // per-T memoized helper that allow /// /// Removes cache data with the specified keys /// - public virtual ValueTask RemoveKeysAsync(ICollection keys, CancellationToken token = default) + public virtual ValueTask RemoveKeysAsync(IEnumerable keys, CancellationToken token = default) { - if (keys is null) + return keys switch { - return default; // for consistency with GetOrCreate/Set: interpret as "none" - } - return keys.Count switch - { - 0 => default, // nothing to do - 1 => RemoveKeyAsync(keys.Single(), token), + // for consistency with GetOrCreate/Set: interpret null as "none" + null or ICollection { Count: 0 } => default, + ICollection { Count: 1 } => RemoveTagAsync(keys.Single(), token), _ => Walk(this, keys, token), }; @@ -95,16 +92,13 @@ static async ValueTask Walk(HybridCache @this, IEnumerable keys, Cancell /// /// Removes cache data associated with the specified tags /// - public virtual ValueTask RemoveTagsAsync(ICollection tags, CancellationToken token = default) + public virtual ValueTask RemoveTagsAsync(IEnumerable tags, CancellationToken token = default) { - if (tags is null) - { - return default; // for consistency with GetOrCreate/Set: interpret as "none" - } - return tags.Count switch + return tags switch { - 0 => default, // nothing to do - 1 => RemoveTagAsync(tags.Single(), token), + // for consistency with GetOrCreate/Set: interpret null as "none" + null or ICollection { Count: 0 } => default, + ICollection { Count: 1 } => RemoveTagAsync(tags.Single(), token), _ => Walk(this, tags, token), }; diff --git a/src/Caching/Hybrid/test/BasicConfig.json b/src/Caching/Hybrid/test/BasicConfig.json new file mode 100644 index 000000000000..374114fb1dba --- /dev/null +++ b/src/Caching/Hybrid/test/BasicConfig.json @@ -0,0 +1,12 @@ +{ + "no_entry_options": { + "MaximumKeyLength": 937 + }, + "with_entry_options": { + "MaximumKeyLength": 937, + "DefaultEntryOptions": { + "LocalCacheExpiration": "00:02:00", + "Flags": "DisableCompression,DisableLocalCacheRead" + } + } +} diff --git a/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj index 8ab5ad30e764..c589f1499cc8 100644 --- a/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj +++ b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj @@ -8,6 +8,13 @@ + + + + + + PreserveNewest + diff --git a/src/Caching/Hybrid/test/ServiceConstructionTests.cs b/src/Caching/Hybrid/test/ServiceConstructionTests.cs index c4244c67df29..6b64765bf336 100644 --- a/src/Caching/Hybrid/test/ServiceConstructionTests.cs +++ b/src/Caching/Hybrid/test/ServiceConstructionTests.cs @@ -1,13 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; using Microsoft.Extensions.Caching.Hybrid.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.DependencyInjection; #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously @@ -16,7 +13,7 @@ namespace Microsoft.Extensions.Caching.Hybrid.Tests; public class ServiceConstructionTests { [Fact] - public void CanCreateService() + public void CanCreateDefaultService() { var services = new ServiceCollection(); services.AddHybridCache(); @@ -24,6 +21,55 @@ public void CanCreateService() Assert.IsType(provider.GetService()); } + [Fact] + public void CanCreateServiceWithManualOptions() + { + var services = new ServiceCollection(); + services.AddHybridCache(options => + { + options.MaximumKeyLength = 937; + options.DefaultEntryOptions = new() { Expiration = TimeSpan.FromSeconds(120), Flags = HybridCacheEntryFlags.DisableLocalCacheRead }; + }); + using var provider = services.BuildServiceProvider(); + var obj = Assert.IsType(provider.GetService()); + var options = obj.Options; + Assert.Equal(937, options.MaximumKeyLength); + var defaults = options.DefaultEntryOptions; + Assert.NotNull(defaults); + Assert.Equal(TimeSpan.FromSeconds(120), defaults.Expiration); + Assert.Equal(HybridCacheEntryFlags.DisableLocalCacheRead, defaults.Flags); + Assert.Null(defaults.LocalCacheExpiration); // wasn't specified + } + + [Fact] + public void CanParseOptions_NoEntryOptions() + { + var source = new JsonConfigurationSource { Path = "BasicConfig.json" }; + var configBuilder = new ConfigurationBuilder { Sources = { source } }; + var config = configBuilder.Build(); + var options = new HybridCacheOptions(); + ConfigurationBinder.Bind(config, "no_entry_options", options); + + Assert.Equal(937, options.MaximumKeyLength); + Assert.Null(options.DefaultEntryOptions); + } + [Fact] + public void CanParseOptions_WithEntryOptions() // in particular, check we can parse the timespan and [Flags] enums + { + var source = new JsonConfigurationSource { Path = "BasicConfig.json" }; + var configBuilder = new ConfigurationBuilder { Sources = { source } }; + var config = configBuilder.Build(); + var options = new HybridCacheOptions(); + ConfigurationBinder.Bind(config, "with_entry_options", options); + + Assert.Equal(937, options.MaximumKeyLength); + var defaults = options.DefaultEntryOptions; + Assert.NotNull(defaults); + Assert.Equal(HybridCacheEntryFlags.DisableCompression | HybridCacheEntryFlags.DisableLocalCacheRead, defaults.Flags); + Assert.Equal(TimeSpan.FromSeconds(120), defaults.LocalCacheExpiration); + Assert.Null(defaults.Expiration); // wasn't specified + } + [Fact] public async Task BasicStatelessUsage() { From 20e1cb251d2d79abd615aa4a277a1d9809db9067 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 16:31:23 +0100 Subject: [PATCH 04/33] demonstrate serializer/factory configuration working --- .../Hybrid/src/Internal/DefaultHybridCache.cs | 20 ++++++ .../Hybrid/test/ServiceConstructionTests.cs | 62 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index a4a3faea64ce..712c79894b4e 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.Extensions.Caching.Hybrid.Internal; @@ -37,4 +39,22 @@ public override ValueTask RemoveTagAsync(string tag, CancellationToken token = d public override ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) => default; // no cache, nothing to set + + internal IHybridCacheSerializer GetSerializer() + { + // unused API, primarily intended to show configuration is working; + // the real version would memoize the result + var service = services.GetServices>().LastOrDefault(); + if (service is null) + { + foreach (var factory in services.GetServices()) + { + if (factory.TryCreateSerializer(out var current)) + { + service = current; + } + } + } + return service ?? throw new InvalidOperationException("No serializer configured for type: " + typeof(T).Name); + } } diff --git a/src/Caching/Hybrid/test/ServiceConstructionTests.cs b/src/Caching/Hybrid/test/ServiceConstructionTests.cs index 6b64765bf336..d9515816f222 100644 --- a/src/Caching/Hybrid/test/ServiceConstructionTests.cs +++ b/src/Caching/Hybrid/test/ServiceConstructionTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Runtime.CompilerServices; using Microsoft.Extensions.Caching.Hybrid.Internal; using Microsoft.Extensions.Configuration; @@ -8,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning disable CS8769 // Nullability of reference types in type of parameter doesn't match implemented member (possibly because of nullability attributes). namespace Microsoft.Extensions.Caching.Hybrid.Tests; public class ServiceConstructionTests @@ -96,5 +98,65 @@ public async Task BasicStatefulUsage() Assert.Equal(expected, actual); } + [Fact] + public void DefaultSerializerConfiguration() + { + var services = new ServiceCollection(); + services.AddHybridCache(); + using var provider = services.BuildServiceProvider(); + var cache = Assert.IsType(provider.GetRequiredService()); + + Assert.IsType(cache.GetSerializer()); + Assert.IsType(cache.GetSerializer()); + Assert.IsType>(cache.GetSerializer()); + Assert.IsType>(cache.GetSerializer()); + } + + [Fact] + public void CustomSerializerConfiguration() + { + var services = new ServiceCollection(); + services.AddHybridCache().WithSerializer(); + using var provider = services.BuildServiceProvider(); + var cache = Assert.IsType(provider.GetRequiredService()); + + Assert.IsType(cache.GetSerializer()); + Assert.IsType>(cache.GetSerializer()); + } + + [Fact] + public void CustomSerializerFactoryConfiguration() + { + var services = new ServiceCollection(); + services.AddHybridCache().WithSerializerFactory(); + using var provider = services.BuildServiceProvider(); + var cache = Assert.IsType(provider.GetRequiredService()); + + Assert.IsType(cache.GetSerializer()); + Assert.IsType>(cache.GetSerializer()); + } + + class Customer { } + class Order { } + + class CustomerSerializer : IHybridCacheSerializer + { + Customer IHybridCacheSerializer.Deserialize(ReadOnlySequence source) => throw new NotImplementedException(); + void IHybridCacheSerializer.Serialize(Customer value, IBufferWriter target) => throw new NotImplementedException(); + } + + class CustomFactory : IHybridCacheSerializerFactory + { + bool IHybridCacheSerializerFactory.TryCreateSerializer(out IHybridCacheSerializer? serializer) + { + if (typeof(T) == typeof(Customer)) + { + serializer = (IHybridCacheSerializer)new CustomerSerializer(); + return true; + } + serializer = null; + return false; + } + } private static string Me([CallerMemberName] string caller = "") => caller; } From 4379647bba4c98697d006394514df74b00486d2f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 16:44:07 +0100 Subject: [PATCH 05/33] move to NuGet only to make the build happier --- eng/SharedFramework.Local.props | 1 - eng/ShippingAssemblies.props | 2 +- .../Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props index ea4d7df14844..46be57d9577b 100644 --- a/eng/SharedFramework.Local.props +++ b/eng/SharedFramework.Local.props @@ -8,7 +8,6 @@ - diff --git a/eng/ShippingAssemblies.props b/eng/ShippingAssemblies.props index 9a84ba0198ba..d0cae638afbb 100644 --- a/eng/ShippingAssemblies.props +++ b/eng/ShippingAssemblies.props @@ -5,7 +5,6 @@ --> - @@ -105,6 +104,7 @@ + diff --git a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj index c7f87a1b5079..c345269ce040 100644 --- a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj +++ b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj @@ -6,7 +6,7 @@ true cache;distributedcache;hybrid true - true + false true true true From d382a7ad080f615bd7d4f24197402bbb89813e09 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 17:54:14 +0100 Subject: [PATCH 06/33] defer on trimming --- .../Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj index c345269ce040..77283688e68c 100644 --- a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj +++ b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj @@ -8,7 +8,6 @@ true false true - true true From e8bc9a9e5624fb9d7b2ee6443b3690c01cb0ef9f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 18:11:05 +0100 Subject: [PATCH 07/33] PR review comments --- src/Caching/Hybrid/src/HybridCacheOptions.cs | 3 ++- src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs | 4 ++-- src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs | 3 +-- src/Caching/Hybrid/src/PublicAPI.Unshipped.txt | 6 +++--- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Caching/Hybrid/src/HybridCacheOptions.cs b/src/Caching/Hybrid/src/HybridCacheOptions.cs index c6274d3491d9..a17a01671d1e 100644 --- a/src/Caching/Hybrid/src/HybridCacheOptions.cs +++ b/src/Caching/Hybrid/src/HybridCacheOptions.cs @@ -28,7 +28,8 @@ public class HybridCacheOptions public bool DisableCompression { get; set; } /// - /// The maximum size of cache items; attempts to store values over this size will be logged. + /// The maximum size of cache items; attempts to store values over this size will be logged + /// and the value will not be stored in cache. /// public long MaximumPayloadBytes { get; set; } = 1 << 20; // 1MiB diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index 712c79894b4e..591d134a63d9 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -28,7 +28,7 @@ public DefaultHybridCache(IOptions options, IDistributedCach } internal HybridCacheOptions Options => options; - public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) + public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) => underlyingDataCallback(state, token); // pass-thru without caching for initial API pass public override ValueTask RemoveKeyAsync(string key, CancellationToken token = default) @@ -37,7 +37,7 @@ public override ValueTask RemoveKeyAsync(string key, CancellationToken token = d public override ValueTask RemoveTagAsync(string tag, CancellationToken token = default) => default; // no cache, nothing to remove - public override ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) + public override ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) => default; // no cache, nothing to set internal IHybridCacheSerializer GetSerializer() diff --git a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs index 6160a2e2ae55..20250f8f031f 100644 --- a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs +++ b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs @@ -11,8 +11,7 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal; internal sealed class InbuiltTypeSerializer : IHybridCacheSerializer, IHybridCacheSerializer { - private static InbuiltTypeSerializer? _instance; - public static InbuiltTypeSerializer Instance => _instance ??= new(); + public static InbuiltTypeSerializer Instance { get; } = new(); string IHybridCacheSerializer.Deserialize(ReadOnlySequence source) { #if NET5_0_OR_GREATER diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index 78901c957cce..ac739b5d4874 100644 --- a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -1,15 +1,15 @@ #nullable enable -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.ICollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveKeyAsync(string! key, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveTagAsync(string! tag, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.SetAsync(string! key, T value, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.ICollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.SetAsync(string! key, T value, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.Set(string! key, System.Buffers.ReadOnlySequence value, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions! options) -> void Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.SetAsync(string! key, System.Buffers.ReadOnlySequence value, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions! options, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGet(string! key, System.Buffers.IBufferWriter! destination) -> bool Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGetAsync(string! key, System.Buffers.IBufferWriter! destination, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Hybrid.HybridCache -Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.ICollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Hybrid.HybridCache.HybridCache() -> void Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 2b71c72401ab..a917028285a8 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -27,7 +27,7 @@ public abstract class HybridCache /// The data, either from cache or the underlying data service [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, - HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default); + HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// /// Get data from the cache, or the underlying data service if not available. @@ -41,7 +41,7 @@ public abstract ValueTask GetOrCreateAsync(string key, TState stat /// The data, either from cache or the underlying data service [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] public ValueTask GetOrCreateAsync(string key, Func> underlyingDataCallback, - HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default) + HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) => GetOrCreateAsync(key, underlyingDataCallback, WrappedCallbackCache.Instance, options, tags, token); private static class WrappedCallbackCache // per-T memoized helper that allows GetOrCreateAsync and GetOrCreateAsync to share an implementation @@ -59,7 +59,7 @@ private static class WrappedCallbackCache // per-T memoized helper that allow /// Additional options for this cache entry /// The tags to associate with this cache item /// Cancellation for this operation - public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, ICollection? tags = null, CancellationToken token = default); + public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// /// Removes cache data with the specified key From 7edddb10aaf46ca36c8266600db5764041535e4d Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 18:13:32 +0100 Subject: [PATCH 08/33] tyop --- src/Caching/Hybrid/src/PublicAPI.Unshipped.txt | 2 +- src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index ac739b5d4874..bb571e4d3840 100644 --- a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -20,7 +20,7 @@ Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableDistributedCach Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCache = Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheRead | Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheWrite -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheRead = 1 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableLocalCacheWrite = 2 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags -Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableUndelyingData = 16 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags +Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableUnderlyingData = 16 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.None = 0 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions.Expiration.get -> System.TimeSpan? diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs index d2521233a72c..5ab2804a57e6 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs @@ -42,7 +42,7 @@ public enum HybridCacheEntryFlags /// /// Only fetch the value from cache - do not attempt to access the underlying data store /// - DisableUndelyingData = 1 << 4, + DisableUnderlyingData = 1 << 4, /// /// Do not compress this payload /// From dc622a8cee8294e52e24a92ba361ebfbb5731f92 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 18:13:45 +0100 Subject: [PATCH 09/33] Update src/Caching/Hybrid/src/Runtime/IsExternalInit.cs Co-authored-by: Andrew Casey --- src/Caching/Hybrid/src/Runtime/IsExternalInit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs b/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs index 9c1f8e7280e5..4c68490902c9 100644 --- a/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs +++ b/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs @@ -7,5 +7,5 @@ namespace System.Runtime.CompilerServices; #if !NET5_0_OR_GREATER [EditorBrowsable(EditorBrowsableState.Never)] -internal static class IsExternalInit { } // for "init" support on down-level TFMs +internal static class IsExternalInit { } // for "init" support on down-level TFMs #endif From 49201828b85c1f26650f9bd2bb22e15418c01a63 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 18:29:45 +0100 Subject: [PATCH 10/33] return a leased array on netfx --- src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs index 20250f8f031f..1ffbd117aa18 100644 --- a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs +++ b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs @@ -42,6 +42,7 @@ void IHybridCacheSerializer.Serialize(string value, IBufferWriter var actual = Encoding.UTF8.GetBytes(value, 0, value.Length, oversized, 0); Debug.Assert(actual == length); target.Write(new(oversized, 0, length)); + ArrayPool.Shared.Return(oversized); #endif } From 8841e97a7b588f6ad76f3ff4c30cadbdab8df4dc Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 18:38:26 +0100 Subject: [PATCH 11/33] prefer ForEach to Walk --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index a917028285a8..2d00ddd910a8 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -76,11 +76,11 @@ public virtual ValueTask RemoveKeysAsync(IEnumerable keys, CancellationT // for consistency with GetOrCreate/Set: interpret null as "none" null or ICollection { Count: 0 } => default, ICollection { Count: 1 } => RemoveTagAsync(keys.Single(), token), - _ => Walk(this, keys, token), + _ => ForEach(this, keys, token), }; // default implementation is to call RemoveKeyAsync for each key in turn - static async ValueTask Walk(HybridCache @this, IEnumerable keys, CancellationToken token) + static async ValueTask ForEach(HybridCache @this, IEnumerable keys, CancellationToken token) { foreach (var key in keys) { @@ -99,11 +99,11 @@ public virtual ValueTask RemoveTagsAsync(IEnumerable tags, CancellationT // for consistency with GetOrCreate/Set: interpret null as "none" null or ICollection { Count: 0 } => default, ICollection { Count: 1 } => RemoveTagAsync(tags.Single(), token), - _ => Walk(this, tags, token), + _ => ForEach(this, tags, token), }; // default implementation is to call RemoveTagAsync for each key in turn - static async ValueTask Walk(HybridCache @this, IEnumerable keys, CancellationToken token) + static async ValueTask ForEach(HybridCache @this, IEnumerable keys, CancellationToken token) { foreach (var key in keys) { From 6cbe02e18f701049ca043c96f66e904c80ed2190 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:01:22 +0100 Subject: [PATCH 12/33] Update src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs Co-authored-by: Brennan --- src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs index 1ffbd117aa18..1abb64d02054 100644 --- a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs +++ b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs @@ -9,6 +9,7 @@ using System.Text; namespace Microsoft.Extensions.Caching.Hybrid.Internal; + internal sealed class InbuiltTypeSerializer : IHybridCacheSerializer, IHybridCacheSerializer { public static InbuiltTypeSerializer Instance { get; } = new(); From 0c0d589213ac604f2a76e4e10e83231c65c88e35 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:02:20 +0100 Subject: [PATCH 13/33] Update src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs Co-authored-by: Brennan --- src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs b/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs index dbe94684d185..e925a033951f 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultJsonSerializerFactory.cs @@ -6,6 +6,7 @@ using System.Text.Json; namespace Microsoft.Extensions.Caching.Hybrid.Internal; + internal sealed class DefaultJsonSerializerFactory : IHybridCacheSerializerFactory { public bool TryCreateSerializer([NotNullWhen(true)] out IHybridCacheSerializer? serializer) From ef0ad56454d96b334897498b54c35c2e1e0fa071 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:02:33 +0100 Subject: [PATCH 14/33] Update src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs Co-authored-by: Brennan --- src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs index 1abb64d02054..a043fc1ca203 100644 --- a/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs +++ b/src/Caching/Hybrid/src/Internal/InbuiltTypeSerializer.cs @@ -13,6 +13,7 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal; internal sealed class InbuiltTypeSerializer : IHybridCacheSerializer, IHybridCacheSerializer { public static InbuiltTypeSerializer Instance { get; } = new(); + string IHybridCacheSerializer.Deserialize(ReadOnlySequence source) { #if NET5_0_OR_GREATER From e28f316e8bf8b57ea76bd8932a342f246e0f70ac Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:09:07 +0100 Subject: [PATCH 15/33] comment nits --- .../src/HybridCacheBuilderExtensions.cs | 10 ++-- src/Caching/Hybrid/src/HybridCacheOptions.cs | 6 ++- .../src/HybridCacheServiceExtensions.cs | 10 ++-- .../Hybrid/src/Internal/DefaultHybridCache.cs | 2 +- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 52 +++++++++---------- .../src/Runtime/HybridCacheEntryFlags.cs | 20 +++---- .../src/Runtime/HybridCacheEntryOptions.cs | 6 +-- .../src/Runtime/IHybridCacheSerializer.cs | 8 +-- .../Runtime/IHybridCacheSerializerFactory.cs | 6 +-- 9 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs b/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs index 3ce69ead2c35..a27240f66418 100644 --- a/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs +++ b/src/Caching/Hybrid/src/HybridCacheBuilderExtensions.cs @@ -12,12 +12,12 @@ namespace Microsoft.Extensions.Caching.Hybrid; /// -/// Configuration extension methods for / +/// Configuration extension methods for / . /// public static class HybridCacheBuilderExtensions { /// - /// Serialize values of type with the specified serializer from + /// Serialize values of type with the specified serializer from . /// public static IHybridCacheBuilder WithSerializer(this IHybridCacheBuilder builder, IHybridCacheSerializer serializer) { @@ -26,7 +26,7 @@ public static IHybridCacheBuilder WithSerializer(this IHybridCacheBuilder bui } /// - /// Serialize values of type with the serializer of type + /// Serialize values of type with the serializer of type . /// public static IHybridCacheBuilder WithSerializer(this IHybridCacheBuilder bui } /// - /// Add as an additional serializer factory, which can provide serializers for multiple types + /// Add as an additional serializer factory, which can provide serializers for multiple types. /// public static IHybridCacheBuilder WithSerializerFactory(this IHybridCacheBuilder builder, IHybridCacheSerializerFactory factory) { @@ -49,7 +49,7 @@ public static IHybridCacheBuilder WithSerializerFactory(this IHybridCacheBuilder } /// - /// Add a factory of type as an additional serializer factory, which can provide serializers for multiple types + /// Add a factory of type as an additional serializer factory, which can provide serializers for multiple types. /// public static IHybridCacheBuilder WithSerializerFactory< #if NET5_0_OR_GREATER diff --git a/src/Caching/Hybrid/src/HybridCacheOptions.cs b/src/Caching/Hybrid/src/HybridCacheOptions.cs index a17a01671d1e..62407b9bf6a9 100644 --- a/src/Caching/Hybrid/src/HybridCacheOptions.cs +++ b/src/Caching/Hybrid/src/HybridCacheOptions.cs @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Caching.Hybrid; /// -/// Options for configuring the default implementation +/// Options for configuring the default implementation. /// public class HybridCacheOptions { @@ -23,7 +23,7 @@ public class HybridCacheOptions public HybridCacheEntryOptions? DefaultEntryOptions { get; set; } /// - /// Disallow compression for this instance + /// Disallow compression for this instance. /// public bool DisableCompression { get; set; } @@ -31,11 +31,13 @@ public class HybridCacheOptions /// The maximum size of cache items; attempts to store values over this size will be logged /// and the value will not be stored in cache. /// + /// The default value is 1 MiB. public long MaximumPayloadBytes { get; set; } = 1 << 20; // 1MiB /// /// The maximum permitted length (in characters) of keys; attempts to use keys over this size will be logged. /// + /// The default value is 1024 characters. public int MaximumKeyLength { get; set; } = 1024; // characters /// diff --git a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs index 26bf37fc1495..019b6d19b19c 100644 --- a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs +++ b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs @@ -15,14 +15,14 @@ namespace Microsoft.Extensions.Caching.Hybrid; /// -/// Configuration extension methods for +/// Configuration extension methods for . /// public static class HybridCacheServiceExtensions { /// - /// Adds support for multi-tier caching services + /// Adds support for multi-tier caching services. /// - /// A builder instance that allows further configuration of the system + /// A builder instance that allows further configuration of the system. public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services, Action setupAction) { #if NET7_0_OR_GREATER @@ -36,9 +36,9 @@ public static IHybridCacheBuilder AddHybridCache(this IServiceCollection service } /// - /// Adds support for multi-tier caching services + /// Adds support for multi-tier caching services. /// - /// A builder instance that allows further configuration of the system + /// A builder instance that allows further configuration of the system. public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services) { #if NET7_0_OR_GREATER diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index 591d134a63d9..e989b94ca725 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal; /// -/// The inbuilt ASP.NET implementation of +/// The inbuilt ASP.NET implementation of . /// internal sealed class DefaultHybridCache : HybridCache { diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 2d00ddd910a8..fab11d095c9b 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -16,15 +16,15 @@ public abstract class HybridCache /// /// Get data from the cache, or the underlying data service if not available. /// - /// The type of the data being considered - /// The type of additional state required by - /// The unique key for this cache entry - /// Provides the underlying data service is the data is not available in the cache - /// Additional state required for - /// Additional options for this cache entry - /// The tags to associate with this cache item - /// Cancellation for this operation - /// The data, either from cache or the underlying data service + /// The type of the data being considered. + /// The type of additional state required by . + /// The unique key for this cache entry. + /// Provides the underlying data service is the data is not available in the cache. + /// Additional state required for . + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// Cancellation for this operation. + /// The data, either from cache or the underlying data service. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); @@ -32,13 +32,13 @@ public abstract ValueTask GetOrCreateAsync(string key, TState stat /// /// Get data from the cache, or the underlying data service if not available. /// - /// The type of the data being considered - /// The unique key for this cache entry - /// Provides the underlying data service is the data is not available in the cache - /// Additional options for this cache entry - /// The tags to associate with this cache item - /// Cancellation for this operation - /// The data, either from cache or the underlying data service + /// The type of the data being considered. + /// The unique key for this cache entry. + /// Provides the underlying data service is the data is not available in the cache. + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// Cancellation for this operation. + /// The data, either from cache or the underlying data service. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] public ValueTask GetOrCreateAsync(string key, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) @@ -53,21 +53,21 @@ private static class WrappedCallbackCache // per-T memoized helper that allow /// /// Manually insert or overwrite a cache entry. /// - /// The type of the data being considered - /// The unique key for this cache entry - /// The value to assign for this cache item - /// Additional options for this cache entry - /// The tags to associate with this cache item - /// Cancellation for this operation + /// The type of the data being considered. + /// The unique key for this cache entry. + /// The value to assign for this cache item. + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// Cancellation for this operation. public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// - /// Removes cache data with the specified key + /// Removes cache data with the specified key. /// public abstract ValueTask RemoveKeyAsync(string key, CancellationToken token = default); /// - /// Removes cache data with the specified keys + /// Removes cache data with the specified keys. /// public virtual ValueTask RemoveKeysAsync(IEnumerable keys, CancellationToken token = default) { @@ -90,7 +90,7 @@ static async ValueTask ForEach(HybridCache @this, IEnumerable keys, Canc } /// - /// Removes cache data associated with the specified tags + /// Removes cache data associated with the specified tags. /// public virtual ValueTask RemoveTagsAsync(IEnumerable tags, CancellationToken token = default) { @@ -113,7 +113,7 @@ static async ValueTask ForEach(HybridCache @this, IEnumerable keys, Canc } /// - /// Removes cache data associated with the specified tag + /// Removes cache data associated with the specified tag. /// public abstract ValueTask RemoveTagAsync(string tag, CancellationToken token = default); } diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs index 5ab2804a57e6..0f2b4e69cc0c 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs @@ -6,45 +6,45 @@ namespace Microsoft.Extensions.Caching.Hybrid; /// -/// Additional flags that apply to a operation +/// Additional flags that apply to a operation. /// [Flags] public enum HybridCacheEntryFlags { /// - /// No additional flags + /// No additional flags. /// None = 0, /// - /// Do not read from the local in-process cache + /// Do not read from the local in-process cache. /// DisableLocalCacheRead = 1 << 0, /// - /// Do not write to the local in-process cache + /// Do not write to the local in-process cache. /// DisableLocalCacheWrite = 1 << 1, /// - /// Do not use the local in-process cache for reads or writes + /// Do not use the local in-process cache for reads or writes. /// DisableLocalCache = DisableLocalCacheRead | DisableLocalCacheWrite, /// - /// Do not read from the secondary distributed cache + /// Do not read from the secondary distributed cache. /// DisableDistributedCacheRead = 1 << 2, /// - /// Do not write to the secondary distributed cache + /// Do not write to the secondary distributed cache. /// DisableDistributedCacheWrite = 1 << 3, /// - /// Do not use the local in-process cache for reads or writes + /// Do not use the local in-process cache for reads or writes. /// DisableDistributedCache = DisableDistributedCacheRead | DisableDistributedCacheWrite, /// - /// Only fetch the value from cache - do not attempt to access the underlying data store + /// Only fetch the value from cache - do not attempt to access the underlying data store. /// DisableUnderlyingData = 1 << 4, /// - /// Do not compress this payload + /// Do not compress this payload. /// DisableCompression = 1 << 5, } diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs index 2d4c1e660f41..6e8491759f92 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs @@ -14,19 +14,19 @@ namespace Microsoft.Extensions.Caching.Hybrid; public sealed class HybridCacheEntryOptions { /// - /// Overall cache duration of this entry, passed to the backend distributed cache + /// Overall cache duration of this entry, passed to the backend distributed cache. /// public TimeSpan? Expiration { get; init; } // overall cache duration /// /// Cache duration in local cache; when retrieving a cached value /// from an external cache store, this value will be used to calculate the local - /// cache expiration, not exceeding the remaining overall cache lifetime + /// cache expiration, not exceeding the remaining overall cache lifetime. /// public TimeSpan? LocalCacheExpiration { get; init; } // TTL in L1 /// - /// Additional flags that apply to this usage + /// Additional flags that apply to this usage. /// public HybridCacheEntryFlags? Flags { get; init; } } diff --git a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs index a0595936e70e..f5c869a71772 100644 --- a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs +++ b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs @@ -6,18 +6,18 @@ namespace Microsoft.Extensions.Caching.Hybrid; /// -/// Per-type serialization/deserialization support for +/// Per-type serialization/deserialization support for . /// -/// The type being serialized/deserialized +/// The type being serialized/deserialized. public interface IHybridCacheSerializer { /// - /// Deserialize a value from the provided + /// Deserialize a value from the provided . /// T Deserialize(ReadOnlySequence source); /// - /// Serialize , writing to the provided + /// Serialize , writing to the provided . /// void Serialize(T value, IBufferWriter target); } diff --git a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs index 98a27c740e35..7e56d5b83835 100644 --- a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs +++ b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs @@ -13,8 +13,8 @@ public interface IHybridCacheSerializerFactory /// /// Request a serializer for the provided type, if possible. /// - /// The type being serialized/deserialized - /// The serializer - /// True if the factory supports this type, False otherwise + /// The type being serialized/deserialized. + /// The serializer. + /// True if the factory supports this type, False otherwise. bool TryCreateSerializer([NotNullWhen(true)] out IHybridCacheSerializer? serializer); } From b49151e9ecc16fa0806cbb8c4ac77b52ae804431 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:12:51 +0100 Subject: [PATCH 16/33] use TimeProvider throughout --- src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs | 4 ---- .../Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs index 019b6d19b19c..c585b2d3320f 100644 --- a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs +++ b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs @@ -47,11 +47,7 @@ public static IHybridCacheBuilder AddHybridCache(this IServiceCollection service _ = services ?? throw new ArgumentNullException(nameof(services)); #endif -#if NET8_0_OR_GREATER services.TryAddSingleton(TimeProvider.System); -#else - services.TryAddSingleton(); -#endif services.AddOptions(); services.AddMemoryCache(); services.AddDistributedMemoryCache(); // we need a backend; use in-proc by default diff --git a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj index 77283688e68c..825815fbf115 100644 --- a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj +++ b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj @@ -21,5 +21,6 @@ + From c2326fddfd178c211574590d708b33612b10c329 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:18:36 +0100 Subject: [PATCH 17/33] more nits --- .../src/Microsoft.Extensions.Caching.Hybrid.csproj | 6 +++++- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 8 ++++---- src/Caching/Hybrid/src/Runtime/IsExternalInit.cs | 11 ----------- 3 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 src/Caching/Hybrid/src/Runtime/IsExternalInit.cs diff --git a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj index 825815fbf115..49671f048347 100644 --- a/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj +++ b/src/Caching/Hybrid/src/Microsoft.Extensions.Caching.Hybrid.csproj @@ -11,6 +11,10 @@ true + + + + @@ -19,7 +23,7 @@ - + diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index fab11d095c9b..84978c2c204e 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -76,11 +76,11 @@ public virtual ValueTask RemoveKeysAsync(IEnumerable keys, CancellationT // for consistency with GetOrCreate/Set: interpret null as "none" null or ICollection { Count: 0 } => default, ICollection { Count: 1 } => RemoveTagAsync(keys.Single(), token), - _ => ForEach(this, keys, token), + _ => ForEachAsync(this, keys, token), }; // default implementation is to call RemoveKeyAsync for each key in turn - static async ValueTask ForEach(HybridCache @this, IEnumerable keys, CancellationToken token) + static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken token) { foreach (var key in keys) { @@ -99,11 +99,11 @@ public virtual ValueTask RemoveTagsAsync(IEnumerable tags, CancellationT // for consistency with GetOrCreate/Set: interpret null as "none" null or ICollection { Count: 0 } => default, ICollection { Count: 1 } => RemoveTagAsync(tags.Single(), token), - _ => ForEach(this, tags, token), + _ => ForEachAsync(this, tags, token), }; // default implementation is to call RemoveTagAsync for each key in turn - static async ValueTask ForEach(HybridCache @this, IEnumerable keys, CancellationToken token) + static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken token) { foreach (var key in keys) { diff --git a/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs b/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs deleted file mode 100644 index 4c68490902c9..000000000000 --- a/src/Caching/Hybrid/src/Runtime/IsExternalInit.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices; - -#if !NET5_0_OR_GREATER -[EditorBrowsable(EditorBrowsableState.Never)] -internal static class IsExternalInit { } // for "init" support on down-level TFMs -#endif From 29dcc2ef7710feada7f5f544c87cc0f64b4036c2 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 11 Apr 2024 19:41:19 +0100 Subject: [PATCH 18/33] regen projects list --- eng/TrimmableProjects.props | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/TrimmableProjects.props b/eng/TrimmableProjects.props index e4ec572527d4..c61ab1b2e4ac 100644 --- a/eng/TrimmableProjects.props +++ b/eng/TrimmableProjects.props @@ -7,7 +7,6 @@ --> - From 4ddba7fcb650edb4a310f24139c259a1a9196a32 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 07:29:57 +0100 Subject: [PATCH 19/33] TryAdd --- src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs index c585b2d3320f..bcbde7462a39 100644 --- a/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs +++ b/src/Caching/Hybrid/src/HybridCacheServiceExtensions.cs @@ -51,10 +51,10 @@ public static IHybridCacheBuilder AddHybridCache(this IServiceCollection service services.AddOptions(); services.AddMemoryCache(); services.AddDistributedMemoryCache(); // we need a backend; use in-proc by default - services.AddSingleton(); - services.AddSingleton>(InbuiltTypeSerializer.Instance); - services.AddSingleton>(InbuiltTypeSerializer.Instance); - services.AddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton>(InbuiltTypeSerializer.Instance); + services.TryAddSingleton>(InbuiltTypeSerializer.Instance); + services.TryAddSingleton(); return new HybridCacheBuilder(services); } } From 427601c0de1b263a732c94e1f8325ef9ac0df993 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 11:47:29 +0100 Subject: [PATCH 20/33] clarify intent of null on Remove{Tags|Keys}Async --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 84978c2c204e..45f219a40265 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -69,6 +69,7 @@ private static class WrappedCallbackCache // per-T memoized helper that allow /// /// Removes cache data with the specified keys. /// + /// Implementors should treat null as empty public virtual ValueTask RemoveKeysAsync(IEnumerable keys, CancellationToken token = default) { return keys switch @@ -92,6 +93,7 @@ static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, /// /// Removes cache data associated with the specified tags. /// + /// Implementors should treat null as empty public virtual ValueTask RemoveTagsAsync(IEnumerable tags, CancellationToken token = default) { return tags switch From 1db2758bd9688d97d4a46c9d8a2e9da599455cd0 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:18:45 +0100 Subject: [PATCH 21/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 45f219a40265..97c322eedc7b 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -51,14 +51,14 @@ private static class WrappedCallbackCache // per-T memoized helper that allow } /// - /// Manually insert or overwrite a cache entry. + /// Asynchronously sets or overwrites the value associated with the key. /// /// The type of the data being considered. - /// The unique key for this cache entry. - /// The value to assign for this cache item. + /// The key of the entry to create. + /// The value to assign for this cache entry. /// Additional options for this cache entry. - /// The tags to associate with this cache item. - /// Cancellation for this operation. + /// The tags to associate with this cache entry. + /// The used to propagate notifications that the operation should be canceled. public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// From 2945ce062fc7084c617f34d0bbbe841cf4d390c8 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:18:58 +0100 Subject: [PATCH 22/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 97c322eedc7b..23b2f7b46139 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -62,7 +62,7 @@ private static class WrappedCallbackCache // per-T memoized helper that allow public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// - /// Removes cache data with the specified key. + /// Asynchronously removes the value associated with the key if it exists. /// public abstract ValueTask RemoveKeyAsync(string key, CancellationToken token = default); From 94d9fe104312ba924e1b200cf3ba540f2d2258e0 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:19:05 +0100 Subject: [PATCH 23/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 23b2f7b46139..f908eb8e48cb 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -67,7 +67,7 @@ private static class WrappedCallbackCache // per-T memoized helper that allow public abstract ValueTask RemoveKeyAsync(string key, CancellationToken token = default); /// - /// Removes cache data with the specified keys. + /// Asynchronously removes the value associated with the key if it exists. /// /// Implementors should treat null as empty public virtual ValueTask RemoveKeysAsync(IEnumerable keys, CancellationToken token = default) From a13e1d33637dc18cc79c7a70181bc853e518e81a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:19:13 +0100 Subject: [PATCH 24/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index f908eb8e48cb..845feb468cbc 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -91,7 +91,7 @@ static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, } /// - /// Removes cache data associated with the specified tags. + /// Asynchronously removes the value associated with the specified tags. /// /// Implementors should treat null as empty public virtual ValueTask RemoveTagsAsync(IEnumerable tags, CancellationToken token = default) From 2a779202dffaed623749a62db1be73898b36d9a1 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:19:46 +0100 Subject: [PATCH 25/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 845feb468cbc..cdaf152cd973 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -26,7 +26,7 @@ public abstract class HybridCache /// Cancellation for this operation. /// The data, either from cache or the underlying data service. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] - public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, + public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> factory, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// From cee8ddcbe9d9dda5c4586fecea3703825943af68 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:19:53 +0100 Subject: [PATCH 26/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index cdaf152cd973..3acaca0b394c 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -14,16 +14,16 @@ namespace Microsoft.Extensions.Caching.Hybrid; public abstract class HybridCache { /// - /// Get data from the cache, or the underlying data service if not available. + /// Asynchronously gets the value associated with the key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found. /// /// The type of the data being considered. /// The type of additional state required by . - /// The unique key for this cache entry. + /// The key of the entry to look for or create. /// Provides the underlying data service is the data is not available in the cache. /// Additional state required for . /// Additional options for this cache entry. /// The tags to associate with this cache item. - /// Cancellation for this operation. + /// The used to propagate notifications that the operation should be canceled. /// The data, either from cache or the underlying data service. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> factory, From 7989f168f7b3d23403845c6cc2963629f781a3bf Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:20:08 +0100 Subject: [PATCH 27/33] Update src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- .../Hybrid/src/Runtime/HybridCacheEntryFlags.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs index 0f2b4e69cc0c..b6a51b11691f 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs @@ -16,35 +16,35 @@ public enum HybridCacheEntryFlags /// None = 0, /// - /// Do not read from the local in-process cache. + /// Disables reading from the local in-process cache. /// DisableLocalCacheRead = 1 << 0, /// - /// Do not write to the local in-process cache. + /// Disables writing to the local in-process cache. /// DisableLocalCacheWrite = 1 << 1, /// - /// Do not use the local in-process cache for reads or writes. + /// Disables both reading from and writing to the local in-process cache. /// DisableLocalCache = DisableLocalCacheRead | DisableLocalCacheWrite, /// - /// Do not read from the secondary distributed cache. + /// Disables reading from the secondary distributed cache. /// DisableDistributedCacheRead = 1 << 2, /// - /// Do not write to the secondary distributed cache. + /// Disables writing to the secondary distributed cache. /// DisableDistributedCacheWrite = 1 << 3, /// - /// Do not use the local in-process cache for reads or writes. + /// Disables both reading from and writing to the secondary distributed cache. /// DisableDistributedCache = DisableDistributedCacheRead | DisableDistributedCacheWrite, /// - /// Only fetch the value from cache - do not attempt to access the underlying data store. + /// Only fetches the value from cache; does not attempt to access the underlying data store. /// DisableUnderlyingData = 1 << 4, /// - /// Do not compress this payload. + /// Disables compression for this payload. /// DisableCompression = 1 << 5, } From 49a477f6155db4cdffc6d3020931263e91fa3060 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:20:17 +0100 Subject: [PATCH 28/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 3acaca0b394c..2215896fa709 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -30,14 +30,14 @@ public abstract ValueTask GetOrCreateAsync(string key, TState stat HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); /// - /// Get data from the cache, or the underlying data service if not available. + /// Asynchronously gets the value associated with the key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found. /// /// The type of the data being considered. - /// The unique key for this cache entry. + /// The key of the entry to look for or create. /// Provides the underlying data service is the data is not available in the cache. /// Additional options for this cache entry. /// The tags to associate with this cache item. - /// Cancellation for this operation. + /// The used to propagate notifications that the operation should be canceled. /// The data, either from cache or the underlying data service. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] public ValueTask GetOrCreateAsync(string key, Func> underlyingDataCallback, From 5f8bcb8a85f78239691a1ae971e6682df7f99fb4 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:20:38 +0100 Subject: [PATCH 29/33] Update src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs index 6e8491759f92..a5416cce9692 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs @@ -6,8 +6,8 @@ namespace Microsoft.Extensions.Caching.Hybrid; /// -/// Additional options (expiration, etc) that apply to a operation. When options -/// can be specified at miltiple levels (for example globally and per-call), the values are composed; the +/// Additional options (expiration, etc.) that apply to a operation. When options +/// can be specified at multiple levels (for example, globally and per-call), the values are composed; the /// most granular non-null value is used, with null values being inherited. If no value is specified at /// any level, the implementation may choose a reasonable default. /// From 7b32e0f6751d229dbfbec5aa1a2ea455cd9206e8 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:20:45 +0100 Subject: [PATCH 30/33] Update src/Caching/Hybrid/src/Runtime/HybridCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 2215896fa709..84db9196c473 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -115,7 +115,7 @@ static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, } /// - /// Removes cache data associated with the specified tag. + /// Asynchronously removes the value associated with the specified tag. /// public abstract ValueTask RemoveTagAsync(string tag, CancellationToken token = default); } From 62b4e6f82c98460fbc2c53a38b0393a48516c81c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:20:57 +0100 Subject: [PATCH 31/33] Update src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs index 887bef0802f6..735545db09a3 100644 --- a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs +++ b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs @@ -20,6 +20,7 @@ public interface IBufferDistributedCache : IDistributedCache /// True if the cache item is found, False otherwise. /// This is functionally similar to , but avoiding the array allocation. bool TryGet(string key, IBufferWriter destination); + /// /// Attempt to asynchronously retrieve an existing cache item. /// From c6cdeae10bb00026a6276f39b0f522d7024f4bcd Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:23:06 +0100 Subject: [PATCH 32/33] Apply suggestions from code review Co-authored-by: Brennan Co-authored-by: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> --- .../Hybrid/src/Internal/DefaultHybridCache.cs | 2 + .../src/Runtime/IBufferDistributedCache.cs | 39 ++++++++++--------- .../Runtime/IHybridCacheSerializerFactory.cs | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index e989b94ca725..18aea6730a0b 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -20,12 +20,14 @@ internal sealed class DefaultHybridCache : HybridCache private readonly IDistributedCache backendCache; private readonly IServiceProvider services; private readonly HybridCacheOptions options; + public DefaultHybridCache(IOptions options, IDistributedCache backendCache, IServiceProvider services) { this.backendCache = backendCache ?? throw new ArgumentNullException(nameof(backendCache)); this.services = services ?? throw new ArgumentNullException(nameof(services)); this.options = options.Value; } + internal HybridCacheOptions Options => options; public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) diff --git a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs index 735545db09a3..994d52766a9d 100644 --- a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs +++ b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs @@ -16,36 +16,37 @@ public interface IBufferDistributedCache : IDistributedCache /// Attempt to retrieve an existing cache item. /// /// The unique key for the cache item. - /// Target to write the cache contents on success. - /// True if the cache item is found, False otherwise. - /// This is functionally similar to , but avoiding the array allocation. + /// The target to write the cache contents on success. + /// true if the cache item is found, false otherwise. + /// This is functionally similar to , but avoids the array allocation. bool TryGet(string key, IBufferWriter destination); /// - /// Attempt to asynchronously retrieve an existing cache item. + /// Asynchronously attempt to retrieve an existing cache entry. /// - /// The unique key for the cache item. - /// Target to write the cache contents on success. - /// Cancellation for this operation. - /// True if the cache item is found, False otherwise. - /// This is functionally similar to , but avoiding the array allocation. + /// The unique key for the cache entry. + /// The target to write the cache contents on success. + /// The used to propagate notifications that the operation should be canceled. + /// true if the cache entry is found, false otherwise. + /// This is functionally similar to , but avoids the array allocation. ValueTask TryGetAsync(string key, IBufferWriter destination, CancellationToken token = default); /// - /// Insert or overwrite a cache item. + /// Sets or overwrites a cache item. /// - /// The unique key for the cache item. - /// The value for this cache item. - /// The cache options for the value. - /// This is functionally similar to , but avoiding the array allocation. + /// The key of the entry to create. + /// The value for this cache entry. + /// The cache options for the entry. + /// This is functionally similar to , but avoids the array allocation. void Set(string key, ReadOnlySequence value, DistributedCacheEntryOptions options); + /// - /// Asynchronously insert or overwrite a cache item. + /// Asynchronously sets or overwrites a cache entry. /// - /// The unique key for the cache item. - /// The value for this cache item. + /// The key of the entry to create. + /// The value for this cache entry. /// The cache options for the value. - /// Cancellation for this operation. - /// This is functionally similar to , but avoiding the array allocation. + /// The used to propagate notifications that the operation should be canceled. + /// This is functionally similar to , but avoids the array allocation. ValueTask SetAsync(string key, ReadOnlySequence value, DistributedCacheEntryOptions options, CancellationToken token = default); } diff --git a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs index 7e56d5b83835..d500ddfb2ba9 100644 --- a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs +++ b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs @@ -15,6 +15,6 @@ public interface IHybridCacheSerializerFactory /// /// The type being serialized/deserialized. /// The serializer. - /// True if the factory supports this type, False otherwise. + /// true if the factory supports this type, false otherwise. bool TryCreateSerializer([NotNullWhen(true)] out IHybridCacheSerializer? serializer); } From d624726a3e406a09bfc7d6e72efb06e2d3423cdd Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 17 Apr 2024 11:30:01 +0100 Subject: [PATCH 33/33] more PR feedback --- .../Hybrid/src/Internal/DefaultHybridCache.cs | 18 +++++++++--------- src/Caching/Hybrid/src/PublicAPI.Unshipped.txt | 4 ++-- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 15 +++++++++------ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index 18aea6730a0b..0ec73b682118 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -17,18 +17,18 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal; /// internal sealed class DefaultHybridCache : HybridCache { - private readonly IDistributedCache backendCache; - private readonly IServiceProvider services; - private readonly HybridCacheOptions options; + private readonly IDistributedCache _backendCache; + private readonly IServiceProvider _services; + private readonly HybridCacheOptions _options; public DefaultHybridCache(IOptions options, IDistributedCache backendCache, IServiceProvider services) { - this.backendCache = backendCache ?? throw new ArgumentNullException(nameof(backendCache)); - this.services = services ?? throw new ArgumentNullException(nameof(services)); - this.options = options.Value; + _backendCache = backendCache ?? throw new ArgumentNullException(nameof(backendCache)); + _services = services ?? throw new ArgumentNullException(nameof(services)); + _options = options.Value; } - internal HybridCacheOptions Options => options; + internal HybridCacheOptions Options => _options; public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) => underlyingDataCallback(state, token); // pass-thru without caching for initial API pass @@ -46,10 +46,10 @@ internal IHybridCacheSerializer GetSerializer() { // unused API, primarily intended to show configuration is working; // the real version would memoize the result - var service = services.GetServices>().LastOrDefault(); + var service = _services.GetService>(); if (service is null) { - foreach (var factory in services.GetServices()) + foreach (var factory in _services.GetServices()) { if (factory.TryCreateSerializer(out var current)) { diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index bb571e4d3840..47e46d6d30ce 100644 --- a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -1,5 +1,5 @@ #nullable enable -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! factory, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveKeyAsync(string! key, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveTagAsync(string! tag, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.SetAsync(string! key, T value, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask @@ -9,7 +9,7 @@ Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.SetAsync(string Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGet(string! key, System.Buffers.IBufferWriter! destination) -> bool Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGetAsync(string! key, System.Buffers.IBufferWriter! destination, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Hybrid.HybridCache -Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, System.Func>! underlyingDataCallback, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, System.Func>! factory, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IReadOnlyCollection? tags = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Hybrid.HybridCache.HybridCache() -> void Microsoft.Extensions.Caching.Hybrid.HybridCacheBuilderExtensions Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags diff --git a/src/Caching/Hybrid/src/Runtime/HybridCache.cs b/src/Caching/Hybrid/src/Runtime/HybridCache.cs index 84db9196c473..a2aaad2c0f26 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Collections; using System.Collections.Generic; @@ -17,10 +20,10 @@ public abstract class HybridCache /// Asynchronously gets the value associated with the key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found. /// /// The type of the data being considered. - /// The type of additional state required by . + /// The type of additional state required by . /// The key of the entry to look for or create. - /// Provides the underlying data service is the data is not available in the cache. - /// Additional state required for . + /// Provides the underlying data service is the data is not available in the cache. + /// Additional state required for . /// Additional options for this cache entry. /// The tags to associate with this cache item. /// The used to propagate notifications that the operation should be canceled. @@ -34,15 +37,15 @@ public abstract ValueTask GetOrCreateAsync(string key, TState stat /// /// The type of the data being considered. /// The key of the entry to look for or create. - /// Provides the underlying data service is the data is not available in the cache. + /// Provides the underlying data service is the data is not available in the cache. /// Additional options for this cache entry. /// The tags to associate with this cache item. /// The used to propagate notifications that the operation should be canceled. /// The data, either from cache or the underlying data service. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] - public ValueTask GetOrCreateAsync(string key, Func> underlyingDataCallback, + public ValueTask GetOrCreateAsync(string key, Func> factory, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) - => GetOrCreateAsync(key, underlyingDataCallback, WrappedCallbackCache.Instance, options, tags, token); + => GetOrCreateAsync(key, factory, WrappedCallbackCache.Instance, options, tags, token); private static class WrappedCallbackCache // per-T memoized helper that allows GetOrCreateAsync and GetOrCreateAsync to share an implementation {