Skip to content

HTTP/3: Http3Stream pooling (builds on top of QUIC stream pooling) #34222

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

Closed
wants to merge 10 commits into from

Conversation

JamesNK
Copy link
Member

@JamesNK JamesNK commented Jul 9, 2021

Note, this PR is built on top of #34075

Making them separate for now to make these changes easier to review.

This PR stashes Http3Stream and friends on transport stream content with a feature. It isn't complete, currently types like the framewriter, output producer and header decoder are recreated each time. A bit more work is required to reuse them as well.


Proposed API

Add PersistentState property to connection context in ASP.NET Core's connection abstraction layer:

public abstract class ConnectionContext : BaseConnectionContext
{
    /// <summary>
    /// Gets or sets a key/value collection that can be used to persist state between connections.
    /// Whether a transport supports persisting state depends on the implementation. The transport must
    /// support pooling and reusing <see cref="ConnectionContext"/> instances for state to be persisted.
    /// <para>
    /// Because values added to persistent state can live in memory until a <see cref="ConnectionContext"/>
    /// is no longer pooled, use caution with this collection to avoid excessive memory use.
    /// </para>
    /// </summary>
    public virtual IDictionary<object, object?> PersistentState { get; set; }
}

This collection is different from the existing Items dictionary because it is not cleared when a ConnectionContext is pooled and reused for a new request.

Usage Examples

This collection is used to persist a Http3Stream on a connection's QUIC streams (represented by ConnectionContext) so they are shared between requests:

Http3Stream<TContext> stream;

if (!streamContext.PersistentState.TryGetValue(StreamPersistentStateKey, out var s))
{
    // New QUIC stream. Create a new Http3Stream and add to persistent state.
    stream = new Http3Stream<TContext>(application, CreateHttpStreamContext(streamContext));
    streamContext.PersistentState.Add(StreamPersistentStateKey, stream);
}
else
{
    // Reused QUIC stream. Get existing Http3Stream and re-initialize it.
    stream = (Http3Stream<TContext>)s!;
    stream.InitializeWithExistingContext(streamContext.Transport);
}

The goal is to reduce per-request allocations with HTTP/3. I haven't measured the difference yet (will measure before merging) but allocations saved per request will be significant (multiple KBs).

Alternative Designs

  • ConnectionContext has existing property bags: Items and Features. These are both reset when a connection is reused and can't be used to persist state between connection instance reuses.
  • Considered adding a generic parameter TContext to ConnectionContext for preserving state (like hosting's IHostContextContainer). Generic spreads everywhere.

@ghost ghost added the area-runtime label Jul 9, 2021
@JamesNK JamesNK changed the title Extract test stream context from test stream types HTTP/3: Http3Stream pooling (builds on top of QUIC stream pooling) Jul 9, 2021
Comment on lines +458 to +462
if (!_streamContextPool.TryDequeue(out var testStreamContext))
{
testStreamContext = new TestStreamContext(canRead: true, canWrite: true, this);
}
testStreamContext.Initialize(GetStreamId(0x00));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestStreamContext is now reused to simulate how QUIC stream contexts are reused.

It is a bit of extra complexity to our tests, but I want to be able to test pooling Http3Streams without having to use Quic transport.


namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
internal sealed class CompletionPipeReader : PipeReader
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy and paste from QUIC transport. Will move into shared code files.

if (stream == null)
{
stream = new Http3Stream<TContext>(application, CreateHttpStreamContext(streamContext));
streamContext.Features.Set<ICachedHttp3StreamFeature<TContext>>(new DefaultCachedHttp3StreamFeature<TContext>(stream));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature collection doesn't make sense as a caching mechanism since the collection itself shouldn't preserve state between uses. You'd want the lower layer to provide an explicate caching feature.

Copy link
Member Author

@JamesNK JamesNK Jul 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a suggestion about how that might work? Would it be a new feature with a dictionary that gets preserved between requests like:

public interface IStateFeature
{
   IDictionary<string, object> State { get; }
}

Or a new explicit dictionary on transport context? Note that BaseConnectionContext already has Features and Items properties - https://github.com/dotnet/aspnetcore/blob/main/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs

I created an issue a while back about using features to preserve the state between requests - #6895. There is a lot of crossover between that and this, just at a different layer.

I don't want to turn this into a big thing so another option is to have a hack in .NET 6 where the stream is added to the transport context's Items with a specific key and that specific key isn't cleared between calls by the QUIC transport. We could then improve it in .NET 7.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state feature and the dictionary on the transport context accomplish the same end, I don't have a preference between them.

I don't want to turn this into a big thing so another option is to have a hack in .NET 6 where the stream is added to the transport context's Items with a specific key and that specific key isn't cleared between calls by the QUIC transport. We could then improve it in .NET 7.

That is hackier than I want to go. Given our http/3 goals for 6.0 emphasize functionality over perf, I'd rather not cache if we can't come up with a clear mechanic for it.

@@ -23,5 +23,9 @@ Microsoft.AspNetCore.Connections.MultiplexedConnectionBuilder.Use(System.Func<Mi
Microsoft.AspNetCore.Connections.MultiplexedConnectionContext
Microsoft.AspNetCore.Connections.MultiplexedConnectionContext.MultiplexedConnectionContext() -> void
Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate
abstract Microsoft.AspNetCore.Connections.MultiplexedConnectionContext.AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext?>
abstract Microsoft.AspNetCore.Connections.MultiplexedConnectionContext.ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection? features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext!>
Microsoft.AspNetCore.Connections.MultiplexedStreamContext
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put it directly on the ConnectionContext

@JamesNK JamesNK force-pushed the jamesnk/http3-http3streampooling branch from 0ee59b3 to fa889da Compare July 12, 2021 08:22
@JamesNK JamesNK force-pushed the jamesnk/http3-http3streampooling branch from fa889da to 6eed9e8 Compare July 12, 2021 08:27
@JamesNK JamesNK added the api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews label Jul 12, 2021
@ghost
Copy link

ghost commented Jul 12, 2021

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@JamesNK JamesNK force-pushed the jamesnk/http3-quicstreampooling branch 2 times, most recently from 45dfcaf to df76007 Compare July 13, 2021 08:51
@pranavkm
Copy link
Contributor

The suggestion is to have add this to Microsoft.Extensions.Features as a new feature type. The connection pooling would be aware of this feature type and re-set it. (not reset it, that's the opposite).

+ namespace Microsoft.AspNetCore.Http.Features
+ {
+     public interface IPersistentStateFeature { } 
+ }

@pranavkm
Copy link
Contributor

+ namespace Microsoft.AspNetCore.Http.Features
+ {
+     public interface IPersistentStateFeature 
+     { 
+         IDictionary<object, object?> State { get; }
+     } 
+ }

@pranavkm pranavkm added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Jul 13, 2021
Base automatically changed from jamesnk/http3-quicstreampooling to main July 16, 2021 03:58
@JamesNK
Copy link
Member Author

JamesNK commented Jul 21, 2021

Replaced by #34576

@JamesNK JamesNK closed this Jul 21, 2021
@JamesNK JamesNK deleted the jamesnk/http3-http3streampooling branch July 24, 2021 21:11
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants