-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Caching: migrate HybridCache api surface from asp.net into runtime #103103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
5b4a0d5
5b93d34
30533c3
4efddb4
d3e8f48
fc619d1
81e10a5
a5b760b
bd83929
e936a64
7a7bfd5
d2f8095
14a3624
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // 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; | ||
|
|
||
| /// <summary> | ||
| /// Provides multi-tier caching services building on <see cref="IDistributedCache"/> backends. | ||
| /// </summary> | ||
| public abstract class HybridCache | ||
| { | ||
| /// <summary> | ||
| /// 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. | ||
| /// </summary> | ||
| /// <typeparam name="TState">The type of additional state required by <paramref name="factory"/>.</typeparam> | ||
| /// <typeparam name="T">The type of the data being considered.</typeparam> | ||
| /// <param name="key">The key of the entry to look for or create.</param> | ||
| /// <param name="factory">Provides the underlying data service is the data is not available in the cache.</param> | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// <param name="state">The state required for <paramref name="factory"/>.</param> | ||
| /// <param name="options">Additional options for this cache entry.</param> | ||
| /// <param name="tags">The tags to associate with this cache item.</param> | ||
| /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | ||
| /// <returns>The data, either from cache or the underlying data service.</returns> | ||
| [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public abstract ValueTask<T> GetOrCreateAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> factory, | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the order of TState / T be changed so that in all overloads the T is first? |
||
| HybridCacheEntryOptions? options = null, IEnumerable<string>? tags = null, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// 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. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the data being considered.</typeparam> | ||
| /// <param name="key">The key of the entry to look for or create.</param> | ||
| /// <param name="factory">Provides the underlying data service is the data is not available in the cache.</param> | ||
| /// <param name="options">Additional options for this cache entry.</param> | ||
| /// <param name="tags">The tags to associate with this cache item.</param> | ||
| /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | ||
| /// <returns>The data, either from cache or the underlying data service.</returns> | ||
| [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")] | ||
| public ValueTask<T> GetOrCreateAsync<T>(string key, Func<CancellationToken, ValueTask<T>> factory, | ||
| HybridCacheEntryOptions? options = null, IEnumerable<string>? tags = null, CancellationToken cancellationToken = default) | ||
| => GetOrCreateAsync(key, factory, WrappedCallbackCache<T>.Instance, options, tags, cancellationToken); | ||
|
|
||
| private static class WrappedCallbackCache<T> // per-T memoized helper that allows GetOrCreateAsync<T> and GetOrCreateAsync<TState, T> 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<Func<CancellationToken, ValueTask<T>>, CancellationToken, ValueTask<T>> Instance = static (callback, ct) => callback(ct); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this actually necessary? I seem to remember the C# compiler folding identical lambdas into the same method definition (but maybe I'm misremembering?) |
||
|
|
||
| /// <summary> | ||
| /// Asynchronously sets or overwrites the value associated with the key. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the data being considered.</typeparam> | ||
| /// <param name="key">The key of the entry to create.</param> | ||
| /// <param name="value">The value to assign for this cache entry.</param> | ||
| /// <param name="options">Additional options for this cache entry.</param> | ||
| /// <param name="tags">The tags to associate with this cache entry.</param> | ||
| /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | ||
| public abstract ValueTask SetAsync<T>(string key, T value, HybridCacheEntryOptions? options = null, IEnumerable<string>? tags = null, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Asynchronously removes the value associated with the key if it exists. | ||
| /// </summary> | ||
| [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 cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Asynchronously removes the value associated with the key if it exists. | ||
| /// </summary> | ||
| /// <remarks>Implementors should treat <c>null</c> as empty</remarks> | ||
| [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<string> keys, CancellationToken cancellationToken = default) | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| return keys switch | ||
| { | ||
| // for consistency with GetOrCreate/Set: interpret null as "none" | ||
| null or ICollection<string> { Count: 0 } => default, | ||
| ICollection<string> { Count: 1 } => RemoveAsync(keys.Single(), cancellationToken), | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _ => ForEachAsync(this, keys, cancellationToken), | ||
| }; | ||
|
|
||
| // default implementation is to call RemoveAsync for each key in turn | ||
| static async ValueTask ForEachAsync(HybridCache @this, IEnumerable<string> keys, CancellationToken cancellationToken) | ||
| { | ||
| foreach (var key in keys) | ||
| { | ||
| await @this.RemoveAsync(key, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any tests to add in this repo for the functionality that's here? |
||
|
|
||
| /// <summary> | ||
| /// Asynchronously removes all values associated with the specified tags. | ||
| /// </summary> | ||
| /// <remarks>Implementors should treat <c>null</c> as empty</remarks> | ||
| [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<string> tags, CancellationToken cancellationToken = default) | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| return tags switch | ||
| { | ||
| // for consistency with GetOrCreate/Set: interpret null as "none" | ||
| null or ICollection<string> { Count: 0 } => default, | ||
| ICollection<string> { 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<string> keys, CancellationToken cancellationToken) | ||
| { | ||
| foreach (var key in keys) | ||
| { | ||
| await @this.RemoveByTagAsync(key, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Asynchronously removes all values associated with the specified tag. | ||
| /// </summary> | ||
| [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 cancellationToken = default); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
||
| /// <summary> | ||
| /// Additional flags that apply to a <see cref="HybridCache"/> operation. | ||
| /// </summary> | ||
| [Flags] | ||
| public enum HybridCacheEntryFlags | ||
| { | ||
| /// <summary> | ||
| /// No additional flags. | ||
| /// </summary> | ||
| None = 0, | ||
| /// <summary> | ||
| /// Disables reading from the local in-process cache. | ||
| /// </summary> | ||
| DisableLocalCacheRead = 1 << 0, | ||
| /// <summary> | ||
| /// Disables writing to the local in-process cache. | ||
| /// </summary> | ||
| DisableLocalCacheWrite = 1 << 1, | ||
| /// <summary> | ||
| /// Disables both reading from and writing to the local in-process cache. | ||
| /// </summary> | ||
| DisableLocalCache = DisableLocalCacheRead | DisableLocalCacheWrite, | ||
| /// <summary> | ||
| /// Disables reading from the secondary distributed cache. | ||
| /// </summary> | ||
| DisableDistributedCacheRead = 1 << 2, | ||
| /// <summary> | ||
| /// Disables writing to the secondary distributed cache. | ||
| /// </summary> | ||
| DisableDistributedCacheWrite = 1 << 3, | ||
| /// <summary> | ||
| /// Disables both reading from and writing to the secondary distributed cache. | ||
| /// </summary> | ||
| DisableDistributedCache = DisableDistributedCacheRead | DisableDistributedCacheWrite, | ||
| /// <summary> | ||
| /// Only fetches the value from cache; does not attempt to access the underlying data store. | ||
| /// </summary> | ||
| DisableUnderlyingData = 1 << 4, | ||
| /// <summary> | ||
| /// Disables compression for this payload. | ||
| /// </summary> | ||
| DisableCompression = 1 << 5, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // 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; | ||
|
|
||
| /// <summary> | ||
| /// Additional options (expiration, etc.) that apply to a <see cref="HybridCache"/> 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. | ||
| /// </summary> | ||
| public sealed class HybridCacheEntryOptions | ||
| { | ||
| /// <summary> | ||
| /// Overall cache duration of this entry, passed to the backend distributed cache. | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| public TimeSpan? Expiration { get; init; } // overall cache duration | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// 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. | ||
| /// </summary> | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public TimeSpan? LocalCacheExpiration { get; init; } // TTL in L1 | ||
|
|
||
| /// <summary> | ||
| /// Additional flags that apply to this usage. | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| public HybridCacheEntryFlags? Flags { get; init; } | ||
|
|
||
| // memoize when possible | ||
| private DistributedCacheEntryOptions? _dc; | ||
| internal DistributedCacheEntryOptions? ToDistributedCacheEntryOptions() | ||
| => Expiration is null ? null : (_dc ??= new() { AbsoluteExpirationRelativeToNow = Expiration }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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.Buffers; | ||
|
|
||
| namespace Microsoft.Extensions.Caching.Hybrid; | ||
|
|
||
| /// <summary> | ||
| /// Per-type serialization/deserialization support for <see cref="HybridCache"/>. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type being serialized/deserialized.</typeparam> | ||
| public interface IHybridCacheSerializer<T> | ||
| { | ||
| /// <summary> | ||
| /// Deserialize a <typeparamref name="T"/> value from the provided <paramref name="source"/>. | ||
| /// </summary> | ||
| T Deserialize(ReadOnlySequence<byte> source); | ||
|
|
||
| /// <summary> | ||
| /// Serialize <paramref name="value"/>, writing to the provided <paramref name="target"/>. | ||
mgravell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| void Serialize(T value, IBufferWriter<byte> target); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
||
| /// <summary> | ||
| /// Factory provider for per-type <see cref="IHybridCacheSerializer{T}"/> instances. | ||
| /// </summary> | ||
| public interface IHybridCacheSerializerFactory | ||
| { | ||
| /// <summary> | ||
| /// Request a serializer for the provided type, if possible. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type being serialized/deserialized.</typeparam> | ||
| /// <param name="serializer">The serializer.</param> | ||
| /// <returns><c>true</c> if the factory supports this type, <c>false</c> otherwise.</returns> | ||
| bool TryCreateSerializer<T>([NotNullWhen(true)] out IHybridCacheSerializer<T>? serializer); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.