From 5a80810d7881a162e7e94aabd7d66f6eadbb7841 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 23 Jul 2024 11:52:59 +0100 Subject: [PATCH 1/6] update to final API approved and merged into runtime --- .../Hybrid/src/Internal/DefaultHybridCache.cs | 10 ++-- .../Hybrid/src/PublicAPI.Unshipped.txt | 14 ++--- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 58 +++++++++---------- .../src/Runtime/HybridCacheEntryOptions.cs | 15 +++-- .../src/Runtime/IHybridCacheSerializer.cs | 3 +- src/Caching/Hybrid/test/SampleUsage.cs | 6 +- src/Caching/Hybrid/test/StampedeTests.cs | 20 +++---- 7 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs index a41c42ba635b..3dc98e55ef3b 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.cs @@ -108,12 +108,12 @@ public DefaultHybridCache(IOptions options, IServiceProvider private HybridCacheEntryFlags GetEffectiveFlags(HybridCacheEntryOptions? options) => (options?.Flags | _hardFlags) ?? _defaultFlags; - public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) + public override ValueTask GetOrCreateAsync(string key, TState state, Func> underlyingDataCallback, HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default) { - var canBeCanceled = token.CanBeCanceled; + var canBeCanceled = cancellationToken.CanBeCanceled; if (canBeCanceled) { - token.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); } var flags = GetEffectiveFlags(options); @@ -141,7 +141,7 @@ public override ValueTask GetOrCreateAsync(string key, TState stat } } - return stampede.JoinAsync(token); + return stampede.JoinAsync(cancellationToken); } public override ValueTask RemoveAsync(string key, CancellationToken token = default) @@ -153,7 +153,7 @@ public override ValueTask RemoveAsync(string key, CancellationToken token = defa public override ValueTask RemoveByTagAsync(string tag, CancellationToken token = default) => default; // tags not yet implemented - public override ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) + public override ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken token = default) { // since we're forcing a write: disable L1+L2 read; we'll use a direct pass-thru of the value as the callback, to reuse all the code; // note also that stampede token is not shared with anyone else diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index 19682fb4a0f2..1a6a9f61fb9d 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>! 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.RemoveAsync(string! key, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveByTagAsync(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 +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! factory, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveAsync(string! key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveByTagAsync(string! tag, System.Threading.CancellationToken cancellationToken = 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.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = 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>! 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.GetOrCreateAsync(string! key, System.Func>! factory, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.Extensions.Caching.Hybrid.HybridCache.HybridCache() -> void Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags.DisableCompression = 32 -> Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryFlags @@ -56,5 +56,5 @@ static Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions.Add static Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions.AddSerializerFactory(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! static Microsoft.Extensions.DependencyInjection.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! static Microsoft.Extensions.DependencyInjection.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! setupAction) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! -virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveAsync(System.Collections.Generic.IEnumerable! keys, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveByTagAsync(System.Collections.Generic.IEnumerable! tags, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveAsync(System.Collections.Generic.IEnumerable! keys, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveByTagAsync(System.Collections.Generic.IEnumerable! tags, System.Threading.CancellationToken cancellationToken = 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 8c239c82106d..5170b01c5ff7 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCache.cs @@ -19,18 +19,18 @@ 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 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. - /// Additional state required for . + /// The 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. + /// 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")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> factory, - HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default); + HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default); /// /// 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. @@ -40,12 +40,12 @@ public abstract ValueTask GetOrCreateAsync(string key, TState stat /// 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 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")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] public ValueTask GetOrCreateAsync(string key, Func> factory, - HybridCacheEntryOptions? options = null, IReadOnlyCollection? tags = null, CancellationToken token = default) - => GetOrCreateAsync(key, factory, WrappedCallbackCache.Instance, options, tags, token); + HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default) + => GetOrCreateAsync(key, factory, WrappedCallbackCache.Instance, options, tags, cancellationToken); private static class WrappedCallbackCache // per-T memoized helper that allows GetOrCreateAsync and GetOrCreateAsync to share an implementation { @@ -61,36 +61,36 @@ private static class WrappedCallbackCache // per-T memoized helper that allow /// The value to assign for this cache entry. /// Additional options for this cache entry. /// 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); + /// The used to propagate notifications that the operation should be canceled. + public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default); /// /// Asynchronously removes the value associated with the key if it exists. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")] - public abstract ValueTask RemoveAsync(string key, CancellationToken token = default); + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] + public abstract ValueTask RemoveAsync(string key, CancellationToken cancellationToken = default); /// /// Asynchronously removes the value associated with the key if it exists. /// /// Implementors should treat null as empty - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")] - public virtual ValueTask RemoveAsync(IEnumerable keys, CancellationToken token = default) + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] + public virtual ValueTask RemoveAsync(IEnumerable keys, CancellationToken cancellationToken = default) { return keys switch { // for consistency with GetOrCreate/Set: interpret null as "none" null or ICollection { Count: 0 } => default, - ICollection { Count: 1 } => RemoveAsync(keys.Single(), token), - _ => ForEachAsync(this, keys, token), + ICollection { Count: 1 } => RemoveAsync(keys.First(), cancellationToken), + _ => ForEachAsync(this, keys, cancellationToken), }; - // default implementation is to call RemoveKeyAsync for each key in turn - static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken token) + // default implementation is to call RemoveAsync for each key in turn + static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken cancellationToken) { foreach (var key in keys) { - await @this.RemoveAsync(key, token).ConfigureAwait(false); + await @this.RemoveAsync(key, cancellationToken).ConfigureAwait(false); } } } @@ -99,23 +99,23 @@ static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, /// Asynchronously removes all values associated with the specified tags. /// /// Implementors should treat null as empty - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")] - public virtual ValueTask RemoveByTagAsync(IEnumerable tags, CancellationToken token = default) + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] + public virtual ValueTask RemoveByTagAsync(IEnumerable tags, CancellationToken cancellationToken = default) { return tags switch { // for consistency with GetOrCreate/Set: interpret null as "none" null or ICollection { Count: 0 } => default, - ICollection { Count: 1 } => RemoveByTagAsync(tags.Single(), token), - _ => ForEachAsync(this, tags, token), + ICollection { Count: 1 } => RemoveByTagAsync(tags.Single(), cancellationToken), + _ => ForEachAsync(this, tags, cancellationToken), }; - // default implementation is to call RemoveTagAsync for each key in turn - static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken token) + // default implementation is to call RemoveByTagAsync for each key in turn + static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken cancellationToken) { foreach (var key in keys) { - await @this.RemoveByTagAsync(key, token).ConfigureAwait(false); + await @this.RemoveByTagAsync(key, cancellationToken).ConfigureAwait(false); } } } @@ -123,6 +123,6 @@ static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, /// /// Asynchronously removes all values associated with the specified tag. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")] - public abstract ValueTask RemoveByTagAsync(string tag, CancellationToken token = default); + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] + public abstract ValueTask RemoveByTagAsync(string tag, CancellationToken cancellationToken = default); } diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs index e5fd18b00699..8bc7343a52c9 100644 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs +++ b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs @@ -15,19 +15,18 @@ namespace Microsoft.Extensions.Caching.Hybrid; public sealed class HybridCacheEntryOptions { /// - /// Overall cache duration of this entry, passed to the backend distributed cache. + /// Gets or set the overall cache duration of this entry, passed to the backend distributed cache. /// - public TimeSpan? Expiration { get; init; } // overall cache duration + public TimeSpan? Expiration { get; init; } - /// - /// Cache duration in local cache; when retrieving a cached value - /// from an external cache store, this value will be used to calculate the local + /// + /// 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 + /// + public TimeSpan? LocalCacheExpiration { get; init; } /// - /// Additional flags that apply to this usage. + /// Gets or sets additional flags that apply to the requested operation. /// public HybridCacheEntryFlags? Flags { get; init; } diff --git a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs index f5c869a71772..5863a0721dff 100644 --- a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs +++ b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs @@ -17,8 +17,7 @@ public interface IHybridCacheSerializer T Deserialize(ReadOnlySequence source); /// - /// Serialize , writing to the provided . + /// Serialize to the provided . /// void Serialize(T value, IBufferWriter target); } - diff --git a/src/Caching/Hybrid/test/SampleUsage.cs b/src/Caching/Hybrid/test/SampleUsage.cs index 8d65d5aeff9f..78786a8b8877 100644 --- a/src/Caching/Hybrid/test/SampleUsage.cs +++ b/src/Caching/Hybrid/test/SampleUsage.cs @@ -121,7 +121,7 @@ public async Task GetSomeInformationAsync(string name, int id, return await cache.GetOrCreateAsync( $"someinfo:{name}:{id}", // unique key for this combination async ct => await SomeExpensiveOperationAsync(name, id, ct), - token: token + cancellationToken: token ); } } @@ -147,7 +147,7 @@ public async Task GetSomeInformationAsync(string name, int id, (name, id), // all of the state we need for the final call, if needed static async (state, token) => await SomeExpensiveOperationAsync(state.name, state.id, token), - token: token + cancellationToken: token ); } } @@ -161,7 +161,7 @@ public async Task GetSomeInformationAsync(string name, int (name, id), // all of the state we need for the final call, if needed static async (state, token) => await SomeExpensiveOperationReuseAsync(state.name, state.id, token), - token: token + cancellationToken: token ); } } diff --git a/src/Caching/Hybrid/test/StampedeTests.cs b/src/Caching/Hybrid/test/StampedeTests.cs index 6fd4bab51f91..bfd777954566 100644 --- a/src/Caching/Hybrid/test/StampedeTests.cs +++ b/src/Caching/Hybrid/test/StampedeTests.cs @@ -87,7 +87,7 @@ public async Task MultipleCallsShareExecution_NoCancellation(int callerCount, bo Interlocked.Increment(ref executeCount); ct.ThrowIfCancellationRequested(); // assert not cancelled return Guid.NewGuid(); - }, token: token).AsTask(); + }, cancellationToken: token).AsTask(); } Assert.Equal(callerCount, cache.DebugGetCallerCount(Me())); @@ -121,7 +121,7 @@ public async Task MultipleCallsShareExecution_NoCancellation(int callerCount, bo Interlocked.Increment(ref executeCount); ct.ThrowIfCancellationRequested(); // assert not cancelled return Guid.NewGuid(); - }, token: token).AsTask(); + }, cancellationToken: token).AsTask(); } Assert.Equal(callerCount, cache.DebugGetCallerCount(Me())); @@ -178,7 +178,7 @@ public async Task MultipleCallsShareExecution_EveryoneCancels(int callerCount) { semaphore.Release(); // handshake so we can check when available again } - }, token: cancels[i].Token).AsTask(); + }, cancellationToken: cancels[i].Token).AsTask(); } Assert.Equal(callerCount, cache.DebugGetCallerCount(Me())); @@ -250,7 +250,7 @@ public async Task MultipleCallsShareExecution_MostCancel(int callerCount, int re { semaphore.Release(); // handshake so we can check when available again } - }, token: cancels[i].Token).AsTask(); + }, cancellationToken: cancels[i].Token).AsTask(); } Assert.Equal(callerCount, cache.DebugGetCallerCount(Me())); @@ -299,8 +299,8 @@ public async Task ImmutableTypesShareFinalTask(bool withCancelation) using var semaphore = new SemaphoreSlim(0); // note AsTask *in this scenario* fetches the underlying incomplete task - var first = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return Guid.NewGuid(); }, token: token).AsTask(); - var second = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return Guid.NewGuid(); }, token: token).AsTask(); + var first = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return Guid.NewGuid(); }, cancellationToken: token).AsTask(); + var second = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return Guid.NewGuid(); }, cancellationToken: token).AsTask(); if (withCancelation) { @@ -325,8 +325,8 @@ public async Task ImmutableCustomTypesShareFinalTask(bool withCancelation) using var semaphore = new SemaphoreSlim(0); // AsTask *in this scenario* fetches the underlying incomplete task - var first = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Immutable(Guid.NewGuid()); }, token: token).AsTask(); - var second = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Immutable(Guid.NewGuid()); }, token: token).AsTask(); + var first = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Immutable(Guid.NewGuid()); }, cancellationToken: token).AsTask(); + var second = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Immutable(Guid.NewGuid()); }, cancellationToken: token).AsTask(); if (withCancelation) { @@ -355,8 +355,8 @@ public async Task MutableTypesNeverShareFinalTask(bool withCancelation) using var semaphore = new SemaphoreSlim(0); // AsTask *in this scenario* fetches the underlying incomplete task - var first = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Mutable(Guid.NewGuid()); }, token: token).AsTask(); - var second = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Mutable(Guid.NewGuid()); }, token: token).AsTask(); + var first = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Mutable(Guid.NewGuid()); }, cancellationToken: token).AsTask(); + var second = cache.GetOrCreateAsync(Me(), async ct => { await semaphore.WaitAsync(CancellationToken.None); semaphore.Release(); return new Mutable(Guid.NewGuid()); }, cancellationToken: token).AsTask(); Assert.NotSame(first, second); semaphore.Release(); From 661ffa7fb32c4b30475331ddc44c024c272d6415 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 23 Jul 2024 11:55:13 +0100 Subject: [PATCH 2/6] extract the API pieces that are now in runtime --- .../Hybrid/src/PublicAPI.Unshipped.txt | 51 ------- src/Caching/Hybrid/src/Runtime/HybridCache.cs | 128 ------------------ .../src/Runtime/HybridCacheEntryFlags.cs | 50 ------- .../src/Runtime/HybridCacheEntryOptions.cs | 37 ----- .../src/Runtime/IBufferDistributedCache.cs | 52 ------- .../src/Runtime/IHybridCacheSerializer.cs | 23 ---- .../Runtime/IHybridCacheSerializerFactory.cs | 20 --- src/Caching/Hybrid/src/Runtime/readme.md | 2 - 8 files changed, 363 deletions(-) delete mode 100644 src/Caching/Hybrid/src/Runtime/HybridCache.cs delete mode 100644 src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs delete mode 100644 src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs delete mode 100644 src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs delete mode 100644 src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs delete mode 100644 src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs delete mode 100644 src/Caching/Hybrid/src/Runtime/readme.md diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index 1a6a9f61fb9d..e9bf9ec0aec7 100644 --- a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -1,53 +1,4 @@ #nullable enable -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync(string! key, TState state, System.Func>! factory, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveAsync(string! key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -abstract Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveByTagAsync(string! tag, System.Threading.CancellationToken cancellationToken = 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.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = 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>! factory, Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, System.Collections.Generic.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -Microsoft.Extensions.Caching.Hybrid.HybridCache.HybridCache() -> void -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.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? -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.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 -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.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 Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions Microsoft.Extensions.DependencyInjection.HybridCacheServiceExtensions static Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions.AddSerializer(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! @@ -56,5 +7,3 @@ static Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions.Add static Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions.AddSerializerFactory(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! static Microsoft.Extensions.DependencyInjection.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! static Microsoft.Extensions.DependencyInjection.HybridCacheServiceExtensions.AddHybridCache(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! setupAction) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! -virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveAsync(System.Collections.Generic.IEnumerable! keys, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -virtual Microsoft.Extensions.Caching.Hybrid.HybridCache.RemoveByTagAsync(System.Collections.Generic.IEnumerable! tags, System.Threading.CancellationToken cancellationToken = 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 deleted file mode 100644 index 5170b01c5ff7..000000000000 --- a/src/Caching/Hybrid/src/Runtime/HybridCache.cs +++ /dev/null @@ -1,128 +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; -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 -{ - /// - /// 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 additional state required by . - /// 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. - /// The 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. - /// 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 = "")] - public abstract ValueTask GetOrCreateAsync(string key, TState state, Func> factory, - HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default); - - /// - /// 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 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. - /// 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 = "")] - public ValueTask GetOrCreateAsync(string key, Func> factory, - HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default) - => GetOrCreateAsync(key, factory, WrappedCallbackCache.Instance, options, tags, cancellationToken); - - 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); - } - - /// - /// Asynchronously sets or overwrites the value associated with the key. - /// - /// The type of the data being considered. - /// 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 entry. - /// The used to propagate notifications that the operation should be canceled. - public abstract ValueTask SetAsync(string key, T value, HybridCacheEntryOptions? options = null, IEnumerable? tags = null, CancellationToken cancellationToken = default); - - /// - /// Asynchronously removes the value associated with the key if it exists. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] - public abstract ValueTask RemoveAsync(string key, CancellationToken cancellationToken = default); - - /// - /// Asynchronously removes the value associated with the key if it exists. - /// - /// Implementors should treat null as empty - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] - public virtual ValueTask RemoveAsync(IEnumerable keys, CancellationToken cancellationToken = default) - { - return keys switch - { - // for consistency with GetOrCreate/Set: interpret null as "none" - null or ICollection { Count: 0 } => default, - ICollection { Count: 1 } => RemoveAsync(keys.First(), cancellationToken), - _ => ForEachAsync(this, keys, cancellationToken), - }; - - // default implementation is to call RemoveAsync for each key in turn - static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken cancellationToken) - { - foreach (var key in keys) - { - await @this.RemoveAsync(key, cancellationToken).ConfigureAwait(false); - } - } - } - - /// - /// Asynchronously removes all values associated with the specified tags. - /// - /// Implementors should treat null as empty - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] - public virtual ValueTask RemoveByTagAsync(IEnumerable tags, CancellationToken cancellationToken = default) - { - return tags switch - { - // for consistency with GetOrCreate/Set: interpret null as "none" - null or ICollection { Count: 0 } => default, - ICollection { Count: 1 } => RemoveByTagAsync(tags.Single(), cancellationToken), - _ => ForEachAsync(this, tags, cancellationToken), - }; - - // default implementation is to call RemoveByTagAsync for each key in turn - static async ValueTask ForEachAsync(HybridCache @this, IEnumerable keys, CancellationToken cancellationToken) - { - foreach (var key in keys) - { - await @this.RemoveByTagAsync(key, cancellationToken).ConfigureAwait(false); - } - } - } - - /// - /// Asynchronously removes all values associated with the specified tag. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "")] - public abstract ValueTask RemoveByTagAsync(string tag, CancellationToken cancellationToken = default); -} diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs deleted file mode 100644 index b6a51b11691f..000000000000 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryFlags.cs +++ /dev/null @@ -1,50 +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; - -namespace Microsoft.Extensions.Caching.Hybrid; - -/// -/// Additional flags that apply to a operation. -/// -[Flags] -public enum HybridCacheEntryFlags -{ - /// - /// No additional flags. - /// - None = 0, - /// - /// Disables reading from the local in-process cache. - /// - DisableLocalCacheRead = 1 << 0, - /// - /// Disables writing to the local in-process cache. - /// - DisableLocalCacheWrite = 1 << 1, - /// - /// Disables both reading from and writing to the local in-process cache. - /// - DisableLocalCache = DisableLocalCacheRead | DisableLocalCacheWrite, - /// - /// Disables reading from the secondary distributed cache. - /// - DisableDistributedCacheRead = 1 << 2, - /// - /// Disables writing to the secondary distributed cache. - /// - DisableDistributedCacheWrite = 1 << 3, - /// - /// Disables both reading from and writing to the secondary distributed cache. - /// - DisableDistributedCache = DisableDistributedCacheRead | DisableDistributedCacheWrite, - /// - /// Only fetches the value from cache; does not attempt to access the underlying data store. - /// - DisableUnderlyingData = 1 << 4, - /// - /// Disables compression for this payload. - /// - DisableCompression = 1 << 5, -} diff --git a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs b/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs deleted file mode 100644 index 8bc7343a52c9..000000000000 --- a/src/Caching/Hybrid/src/Runtime/HybridCacheEntryOptions.cs +++ /dev/null @@ -1,37 +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; -using Microsoft.Extensions.Caching.Distributed; - -namespace Microsoft.Extensions.Caching.Hybrid; - -/// -/// 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. -/// -public sealed class HybridCacheEntryOptions -{ - /// - /// Gets or set the overall cache duration of this entry, passed to the backend distributed cache. - /// - public TimeSpan? Expiration { get; init; } - - /// - /// 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; } - - /// - /// Gets or sets additional flags that apply to the requested operation. - /// - public HybridCacheEntryFlags? Flags { get; init; } - - // memoize when possible - private DistributedCacheEntryOptions? _dc; - internal DistributedCacheEntryOptions? ToDistributedCacheEntryOptions() - => Expiration is null ? null : (_dc ??= new() { AbsoluteExpirationRelativeToNow = Expiration }); -} diff --git a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs b/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs deleted file mode 100644 index 994d52766a9d..000000000000 --- a/src/Caching/Hybrid/src/Runtime/IBufferDistributedCache.cs +++ /dev/null @@ -1,52 +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.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. - /// 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); - - /// - /// Asynchronously attempt to retrieve an existing cache entry. - /// - /// 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); - - /// - /// Sets or overwrites a cache item. - /// - /// 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 sets or overwrites a cache entry. - /// - /// The key of the entry to create. - /// The value for this cache entry. - /// The cache options for the value. - /// 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/IHybridCacheSerializer.cs b/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs deleted file mode 100644 index 5863a0721dff..000000000000 --- a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializer.cs +++ /dev/null @@ -1,23 +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.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 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 deleted file mode 100644 index d500ddfb2ba9..000000000000 --- a/src/Caching/Hybrid/src/Runtime/IHybridCacheSerializerFactory.cs +++ /dev/null @@ -1,20 +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.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/readme.md b/src/Caching/Hybrid/src/Runtime/readme.md deleted file mode 100644 index 1e2289449f0b..000000000000 --- a/src/Caching/Hybrid/src/Runtime/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -These types are intended to be added to be relocated to `Microsoft.Extensions.Caching.Abstractions`; their inclusion -here is a preview placeholder From 7e6aa49878491828193bed96cd5a09ed848465e5 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 23 Jul 2024 13:03:20 +0100 Subject: [PATCH 3/6] work around ToDistributedCacheEntryOptions memoize utility method --- .../Hybrid/src/Internal/DefaultHybridCache.L2.cs | 12 +++++++++++- src/Caching/Hybrid/test/L2Tests.cs | 16 +++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.L2.cs b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.L2.cs index 578528e9d4be..e0480abff5b8 100644 --- a/src/Caching/Hybrid/src/Internal/DefaultHybridCache.L2.cs +++ b/src/Caching/Hybrid/src/Internal/DefaultHybridCache.L2.cs @@ -106,9 +106,19 @@ private DistributedCacheEntryOptions GetOptions(HybridCacheEntryOptions? options DistributedCacheEntryOptions? result = null; if (options is not null && options.Expiration.HasValue && options.Expiration.GetValueOrDefault() != _defaultExpiration) { - result = options.ToDistributedCacheEntryOptions(); + result = ToDistributedCacheEntryOptions(options); } return result ?? _defaultDistributedCacheExpiration; + +#if NET8_0_OR_GREATER + // internal method memoizes this allocation; since it is "init", it is immutable (outside reflection) + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = nameof(ToDistributedCacheEntryOptions))] + extern static DistributedCacheEntryOptions? ToDistributedCacheEntryOptions(HybridCacheEntryOptions options); +#else + // withoug that helper method, we'll just eat the alloc (down-level TFMs) + static DistributedCacheEntryOptions ToDistributedCacheEntryOptions(HybridCacheEntryOptions options) + => new() { AbsoluteExpirationRelativeToNow = options.Expiration }; +#endif } internal void SetL1(string key, CacheItem value, HybridCacheEntryOptions? options) diff --git a/src/Caching/Hybrid/test/L2Tests.cs b/src/Caching/Hybrid/test/L2Tests.cs index 6fd77c0707a6..378e5c8ed4ba 100644 --- a/src/Caching/Hybrid/test/L2Tests.cs +++ b/src/Caching/Hybrid/test/L2Tests.cs @@ -38,7 +38,9 @@ static string CreateString(bool work = false) return Guid.NewGuid().ToString(); } - static readonly HybridCacheEntryOptions _noL1 = new() { Flags = HybridCacheEntryFlags.DisableLocalCache }; + private static readonly HybridCacheEntryOptions Expiry = new() { Expiration = TimeSpan.FromMinutes(3.5) }; + + private static readonly HybridCacheEntryOptions ExpiryNoL1 = new() { Flags = HybridCacheEntryFlags.DisableLocalCache, Expiration = TimeSpan.FromMinutes(3.5) }; [Theory] [InlineData(true)] @@ -63,7 +65,7 @@ public async Task AssertL2Operations_Immutable(bool buffers) Log.WriteLine("Reading without L1..."); for (var i = 0; i < 5; i++) { - var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(CreateString()), _noL1); + var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(CreateString()), ExpiryNoL1); Assert.Equal(s, x); Assert.NotSame(s, x); } @@ -103,13 +105,13 @@ public async Task AssertL2Operations_Mutable(bool buffers) using var provider = GetDefaultCache(buffers, out var cache); var backend = Assert.IsAssignableFrom(cache.BackendCache); Log.WriteLine("Inventing key..."); - var s = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString(true) })); + var s = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString(true) }), Expiry); Assert.Equal(2, backend.OpCount); // GET, SET Log.WriteLine("Reading with L1..."); for (var i = 0; i < 5; i++) { - var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString() })); + var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString() }), Expiry); Assert.Equal(s.Value, x.Value); Assert.NotSame(s, x); } @@ -118,7 +120,7 @@ public async Task AssertL2Operations_Mutable(bool buffers) Log.WriteLine("Reading without L1..."); for (var i = 0; i < 5; i++) { - var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString() }), _noL1); + var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString() }), ExpiryNoL1); Assert.Equal(s.Value, x.Value); Assert.NotSame(s, x); } @@ -129,7 +131,7 @@ public async Task AssertL2Operations_Mutable(bool buffers) await cache.SetAsync(Me(), s); for (var i = 0; i < 5; i++) { - var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString() })); + var x = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString() }), Expiry); Assert.Equal(s.Value, x.Value); Assert.NotSame(s, x); } @@ -140,7 +142,7 @@ public async Task AssertL2Operations_Mutable(bool buffers) Assert.Equal(9, backend.OpCount); // DEL Log.WriteLine("Fetching new..."); - var t = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString(true) })); + var t = await cache.GetOrCreateAsync(Me(), ct => new ValueTask(new Foo { Value = CreateString(true) }), Expiry); Assert.NotEqual(s.Value, t.Value); Assert.Equal(11, backend.OpCount); // GET, SET } From 9d243dc4ed65e298c47c67d987f240b8b1a0d365 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 24 Jul 2024 16:39:07 +0100 Subject: [PATCH 4/6] add back missing APIs incorrectly deleted --- src/Caching/Hybrid/src/PublicAPI.Unshipped.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt index e9bf9ec0aec7..abe172bda56a 100644 --- a/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt +++ b/src/Caching/Hybrid/src/PublicAPI.Unshipped.txt @@ -1,4 +1,18 @@ #nullable enable +Microsoft.Extensions.Caching.Hybrid.HybridCacheOptions +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 +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.IHybridCacheBuilder +Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection! Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions Microsoft.Extensions.DependencyInjection.HybridCacheServiceExtensions static Microsoft.Extensions.DependencyInjection.HybridCacheBuilderExtensions.AddSerializer(this Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! builder) -> Microsoft.Extensions.Caching.Hybrid.IHybridCacheBuilder! From f12be00caee618d25cd29567e9c732d2b4d7b25a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 24 Jul 2024 17:06:26 +0100 Subject: [PATCH 5/6] fixup csproj - no longer need framework backref --- .../test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj | 1 + .../src/Microsoft.Extensions.Caching.SqlServer.csproj | 3 --- .../src/Microsoft.Extensions.Caching.StackExchangeRedis.csproj | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) 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 54c0de3adf67..4b862961ffa3 100644 --- a/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj +++ b/src/Caching/Hybrid/test/Microsoft.Extensions.Caching.Hybrid.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Caching/SqlServer/src/Microsoft.Extensions.Caching.SqlServer.csproj b/src/Caching/SqlServer/src/Microsoft.Extensions.Caching.SqlServer.csproj index 21c98b778833..3088ff699532 100644 --- a/src/Caching/SqlServer/src/Microsoft.Extensions.Caching.SqlServer.csproj +++ b/src/Caching/SqlServer/src/Microsoft.Extensions.Caching.SqlServer.csproj @@ -20,9 +20,6 @@ - - - diff --git a/src/Caching/StackExchangeRedis/src/Microsoft.Extensions.Caching.StackExchangeRedis.csproj b/src/Caching/StackExchangeRedis/src/Microsoft.Extensions.Caching.StackExchangeRedis.csproj index 3a3faeaf9f40..c3f7de250341 100644 --- a/src/Caching/StackExchangeRedis/src/Microsoft.Extensions.Caching.StackExchangeRedis.csproj +++ b/src/Caching/StackExchangeRedis/src/Microsoft.Extensions.Caching.StackExchangeRedis.csproj @@ -15,9 +15,6 @@ - - - From a8764fbaeb214d9b7efe78c5a885a47cf06a69eb Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 25 Jul 2024 13:31:59 +0100 Subject: [PATCH 6/6] add missing package-ref to microbenchmark --- .../Microsoft.Extensions.Caching.MicroBenchmarks.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Caching/perf/MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks.csproj b/src/Caching/perf/MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks.csproj index 50b8df19317e..40a058e7dc13 100644 --- a/src/Caching/perf/MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks.csproj +++ b/src/Caching/perf/MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks.csproj @@ -16,6 +16,8 @@ + +