Add IOutputCacheBufferWriterStore
See original GitHub issueIdeally for preview 6…
Background and Motivation
We wish to implement an official SE.Redis output cache implementation.
The existing IOutputCacheStore
API is byte[]
-focused, which is fine for the in-process implementation which constantly returns the same byte[]
, but is hugely suboptimal for out-of-process implementations, as we would have to return a right-sized byte[]
every call (in either direction), causing GC load. Additionally, since output-cache is an entire page load plus headers, this can easily be large enough to spill into LOH.
Context: https://github.com/dotnet/aspnetcore/pull/48450
For this, we propose three things:
- a new
SetAsync
method similar to the existing, but takingReadOnlySequence<byte>
instead ofbyte[]
; this API to be implemented in the existingIOutputCacheStore
interface as a DIM, to avoid back-compat problems - a new interface
IOutputCacheBufferWriterStore
which extendsIOutputCacheStore
(same assembly/namespace), allowing implementations to optionally use a newGetAsync
API that takes anIBufferWriter<byte>
- thus allowing implementations to pass data to the consumer while site-stepping the topic of “data lifetime” : the receiver can pass a recycling-enabled buffer-writer, and the cache implementation does not need to know about those details - it just asks for space to write data, and writes - a new API to register the SE.Redis output cache implementation, in the existing Microsoft.Extensions.Caching.StackExchangeRedis OOB package
Proposed API
namespace Microsoft.AspNetCore.OutputCaching;
public interface IOutputCacheStore
{
// pre-existing
ValueTask SetAsync(string key, byte[] value, string[]? tags, TimeSpan validFor, CancellationToken cancellationToken);
+ ValueTask SetAsync(string key, ReadOnlySequence<byte> value, ReadOnlyMemory<string> tags, TimeSpan validFor, CancellationToken cancellationToken)
+ {
+ // compatibility implementation using the original API
+ return SetAsync(key, value.ToArray(), tags.IsEmpty ? null : tags.ToArray(), validFor, cancellationToken);
+ }
}
+ public interface IOutputCacheBufferWriterStore : IOutputCacheStore
+ {
+ ValueTask<bool> GetAsync(string key, IBufferWriter<byte> destination, CancellationToken cancellationToken);
+ }
This second interface is optional / separate because not all types will wish to opt in; in particular, in the case of the existing in-proc implementation, it will be preferable to just keep returning the existing byte[]
; as such, the output cache middleware will test for this API and use the most appropriate.
Additionally, to implement the SE.Redis cache, we propose the addition of
package: Microsoft.Extensions.Caching.StackExchangeRedis
(pre-existing)
type StackExchangeRedisCacheServiceCollectionExtensions
(pre-existing)
+ public static IServiceCollection AddStackExchangeRedisOutputCache(
+ this IServiceCollection services, Action<RedisCacheOptions> setupAction)
which is the new API to register SE.Redis into output cache
Alternatives considered
- eating the GC overhead, zero API change - hugely undesirable in this context
- have a new
GetAsync
API that returns some kind of “data with lifetime” - however, there’s no good metaphor for this, and it pushes the buffer management into each individual buffer implementation, rather than having one buffer implementation and having the cache implementations just worry about cache
Issue Analytics
- State:
- Created 3 months ago
- Reactions:1
- Comments:7 (7 by maintainers)
API Review Notes:
IBufferWriter<byte> destination
?out
, but it seems to align.AddStackExchangeRedisOutputCache
?AddStackExchangeRedisCache
which addsIDistributedCache
lives.IOutputCacheStore
byte[] methods and not something that just throws then.IOutputCacheBufferStore
?IBufferWriter
anymore but the pipe still gives you buffers.API Approved!
@mitchdenny TryGetAsync?