-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Caching: migrate HybridCache api surface from asp.net into runtime (#…
…103103) * HybridCache migration from aspnet * use cancellationToken instead of token * reapply dotnet/aspnetcore#56719 * Update src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs Co-authored-by: David Cantú <[email protected]> * Update src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs Co-authored-by: David Cantú <[email protected]> * Update src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs Co-authored-by: David Cantú <[email protected]> * prefer throw null * use IEnumerable<string> instead of IReadOnlyCollection<string> * remove suppressions * Update src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/IHybridCacheSerializer.cs Co-authored-by: Stephen Toub <[email protected]> * Update src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCacheEntryOptions.cs Co-authored-by: Stephen Toub <[email protected]> * Update src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCacheEntryOptions.cs Co-authored-by: Stephen Toub <[email protected]> * PR nits --------- Co-authored-by: David Cantú <[email protected]> Co-authored-by: Stephen Toub <[email protected]>
- Loading branch information
1 parent
4c21cb3
commit ea9d53e
Showing
9 changed files
with
367 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// 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> | ||
/// <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> | ||
public abstract ValueTask<T> GetOrCreateAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> factory, | ||
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> | ||
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); | ||
} | ||
|
||
/// <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> | ||
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> | ||
public virtual ValueTask RemoveAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default) | ||
{ | ||
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.First(), cancellationToken), | ||
_ => 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); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Asynchronously removes all values associated with the specified tags. | ||
/// </summary> | ||
/// <remarks>Implementors should treat <c>null</c> as empty</remarks> | ||
public virtual ValueTask RemoveByTagAsync(IEnumerable<string> tags, CancellationToken cancellationToken = default) | ||
{ | ||
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> | ||
public abstract ValueTask RemoveByTagAsync(string tag, CancellationToken cancellationToken = default); | ||
} |
50 changes: 50 additions & 0 deletions
50
src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCacheEntryFlags.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
37 changes: 37 additions & 0 deletions
37
...libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCacheEntryOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
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> | ||
/// Gets or set the overall cache duration of this entry, passed to the backend distributed cache. | ||
/// </summary> | ||
public TimeSpan? Expiration { get; init; } | ||
|
||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public TimeSpan? LocalCacheExpiration { get; init; } | ||
|
||
/// <summary> | ||
/// Gets or sets additional flags that apply to the requested operation. | ||
/// </summary> | ||
public HybridCacheEntryFlags? Flags { get; init; } | ||
|
||
// memoize when possible | ||
private DistributedCacheEntryOptions? _dc; | ||
internal DistributedCacheEntryOptions? ToDistributedCacheEntryOptions() | ||
=> Expiration is null ? null : (_dc ??= new() { AbsoluteExpirationRelativeToNow = Expiration }); | ||
} |
23 changes: 23 additions & 0 deletions
23
src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/IHybridCacheSerializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"/> to the provided <paramref name="target"/>. | ||
/// </summary> | ||
void Serialize(T value, IBufferWriter<byte> target); | ||
} |
20 changes: 20 additions & 0 deletions
20
...ies/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/IHybridCacheSerializerFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
52 changes: 52 additions & 0 deletions
52
src/libraries/Microsoft.Extensions.Caching.Abstractions/src/IBufferDistributedCache.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// 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 | ||
|
||
/// <summary> | ||
/// Represents a distributed cache of serialized values, with support for low allocation data transfer. | ||
/// </summary> | ||
public interface IBufferDistributedCache : IDistributedCache | ||
{ | ||
/// <summary> | ||
/// Attempt to retrieve an existing cache item. | ||
/// </summary> | ||
/// <param name="key">The unique key for the cache item.</param> | ||
/// <param name="destination">The target to write the cache contents on success.</param> | ||
/// <returns><c>true</c> if the cache item is found, <c>false</c> otherwise.</returns> | ||
/// <remarks>This is functionally similar to <see cref="IDistributedCache.Get(string)"/>, but avoids the array allocation.</remarks> | ||
bool TryGet(string key, IBufferWriter<byte> destination); | ||
|
||
/// <summary> | ||
/// Asynchronously attempt to retrieve an existing cache entry. | ||
/// </summary> | ||
/// <param name="key">The unique key for the cache entry.</param> | ||
/// <param name="destination">The target to write the cache contents on success.</param> | ||
/// <param name="token">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | ||
/// <returns><c>true</c> if the cache entry is found, <c>false</c> otherwise.</returns> | ||
/// <remarks>This is functionally similar to <see cref="IDistributedCache.GetAsync(string, CancellationToken)"/>, but avoids the array allocation.</remarks> | ||
ValueTask<bool> TryGetAsync(string key, IBufferWriter<byte> destination, CancellationToken token = default); | ||
|
||
/// <summary> | ||
/// Sets or overwrites a cache item. | ||
/// </summary> | ||
/// <param name="key">The key of the entry to create.</param> | ||
/// <param name="value">The value for this cache entry.</param> | ||
/// <param name="options">The cache options for the entry.</param> | ||
/// <remarks>This is functionally similar to <see cref="IDistributedCache.Set(string, byte[], DistributedCacheEntryOptions)"/>, but avoids the array allocation.</remarks> | ||
void Set(string key, ReadOnlySequence<byte> value, DistributedCacheEntryOptions options); | ||
|
||
/// <summary> | ||
/// Asynchronously sets or overwrites a cache entry. | ||
/// </summary> | ||
/// <param name="key">The key of the entry to create.</param> | ||
/// <param name="value">The value for this cache entry.</param> | ||
/// <param name="options">The cache options for the value.</param> | ||
/// <param name="token">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | ||
/// <remarks>This is functionally similar to <see cref="IDistributedCache.SetAsync(string, byte[], DistributedCacheEntryOptions, CancellationToken)"/>, but avoids the array allocation.</remarks> | ||
ValueTask SetAsync(string key, ReadOnlySequence<byte> value, DistributedCacheEntryOptions options, CancellationToken token = default); | ||
} |
Oops, something went wrong.