-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Reuse HttpContext object per HTTP/1 connection and HTTP/2 stream #6424
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
Conversation
src/Servers/Kestrel/Core/src/Internal/KestrelHttpContextFactory.cs
Outdated
Show resolved
Hide resolved
@@ -535,14 +540,24 @@ private async Task ProcessRequests<TContext>(IHttpApplication<TContext> applicat | |||
|
|||
InitializeStreams(messageBody); | |||
|
|||
var httpContext = application.CreateContext(this); | |||
// Initialize the HttpContext before we call into the IHttpApplication | |||
if (_httpContext is null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a step too far I can revert this commit and go back to this one 8526622. The difference is that it would lazily initialize the http context in the rare case where the IHttpContextFactory is overridden by the user. It means this field would always be null instead of being assigned and those extra calls would be avoided.
Before: 1,927,129 4% regression
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a fan of the blanket copy paste, it creates a long term maintenance problem. You can't derive from DefaultHttpContext and then call base, right?
src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
Outdated
Show resolved
Hide resolved
That defeats the purpose. This change effectively makes DefaultHttpContext legacy without breaking existing consumers of it. |
- Today in Kestrel, we reuse the IFeatureCollection per connection and per Http2Stream. This PR aims to take advantage of that same technique and affinitize the HttpContext and friends so that they are only allocated per connection. - ReusableHttpContext and friends mimic the functionality of DefaultHttpContext but is sealed and has no overridable methods.
de3fa99
to
1623dcf
Compare
- Allows servers to cache the HttpContext and friends across requests. - Remove KestrelHttpContextFactory
@Tratcher 🆙 📅 |
src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
Outdated
Show resolved
Hide resolved
{ | ||
get | ||
{ | ||
if (_httpContext is null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this change might be a bit dangerous if this is accessed during the request, it'll re-initialize?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean if it's accessed outside of the request flow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inside. If you get the HttpContext and cast the FeatureCollection property to IHttpContextContainer then it’ll call Initialize again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create it upfront in the variable
private ReusableHttpContext _httpContext = new ReusableHttpContext(this);
Add an IsInitialized
flag in ReusableHttpContext
and use that in the property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what you're getting at. You mean something like the following?
app.Run(async context =>
{
var context2 = ((IHttpContextContainer)context.Features).HttpContext;
await context.Response.WriteAsync(object.ReferenceEquals(context, context2).ToString());
});
In that case the response would be "True". I'm not sure why you'd think this wouldn't be the case.
I'm also not sure why we're concerned about what happens when the IFeatureCollection to IHttpContextContainer. I don't know why we'd have any burden whatsoever to support that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After seeing @benaadams's comment, I understand. Adding a flag that get's reset between requests would certainly fix the issue with ReusableHttpContext.Initialize() getting called multiple times in a request, but I still don't think app code casting the IFeatureCollection to an IHttpContextContainer is a very realistic scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's super edge case. I'm going to merge this anyways, I just wanted to bring it up.
cc @benaadams
PS: I looked at doing this in the default HttpContextFactory impl with a feature based approach but there's no place to store the HttpContext that isn't per request in other servers.
I'll try to get some perf results for allocations and throughput as well.
Here's a profile before all of the optimizations we've done (including a bunch from @benaadams):
I'm still waiting on https://github.com/dotnet/coreclr/issues/21559 to be fixed to get an equivalent profile. Here's what I expect allocation wise:
byte[]
allocations were cut in 1/2 because of this cb1917a and will be fully eliminated once we do the pipelines transition (we'll be using Kestrel buffers instead of allocating temporary ones).That leaves us with the following allocations:
Here's what I got when looking at allocations with dotTrace's timeline view (thanks @pakrym)
Before
After
We removed 35MB from the small object heap with this change.
Strings
As for the strings It's all headers and path: