-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Optimizing ChunkingCookieManager #31625
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
Optimizing ChunkingCookieManager #31625
Conversation
{ | ||
if (_logger == null) | ||
{ | ||
var services = _features.Get<Features.IServiceProvidersFeature>()?.RequestServices; |
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.
We want to log from these components like this.
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.
Thanks for the review, I kind of feel there is something missing after "like this", could you please tell me if I am missing something here?
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.
@davidfowl this was copied from the existing code above, we can discuss it separately from this PR.
aspnetcore/src/Http/Http/src/Internal/ResponseCookies.cs
Lines 59 to 71 in 00b551e
if (!options.Secure && options.SameSite == SameSiteMode.None) | |
{ | |
if (_logger == null) | |
{ | |
var services = _features.Get<Features.IServiceProvidersFeature>()?.RequestServices; | |
_logger = services?.GetService<ILogger<ResponseCookies>>(); | |
} | |
if (_logger != null) | |
{ | |
Log.SameSiteCookieNotSecure(_logger, key); | |
} | |
} |
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.
Correct I did not fully look at this yet and just copied from the original append
but I will definitely look if this can be improved too, thanks
}; | ||
|
||
var cookierHeaderValue = setCookieHeaderValue.ToString()[1..]; | ||
var cookies = new string[keyValuePairs.Count]; |
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 think you could borrow this from the ArrayPool. Alternatively you could simply call Append
n times. It's effectively what the IHeaderDictionary.Append(IList<T>)
seems to do:
aspnetcore/src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs
Lines 139 to 144 in 5b3ba86
var newValues = new string[values.Count]; | |
for (var i = 0; i < values.Count; i++) | |
{ | |
newValues[i] = values[i]!.ToString()!; | |
} | |
Headers.Append(name, new StringValues(newValues)); |
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.
In respect to ArrayPool
:
It sounded cool so I tried it using ArrayPool<string>.Shared.Rent(keyValuePairs.Count())
but it actually increases the mean
about 100ns-200ns.
In respect to Headers.Append
:
aspnetcore/src/Http/Http/src/Internal/ResponseCookies.cs
Lines 133 to 139 in 9a55381
foreach (var keyValuePair in keyValuePairs) | |
{ | |
cookies[position] = string.Concat(_enableCookieNameEncoding ? Uri.EscapeDataString(keyValuePair.Key) : keyValuePair.Key, "=", Uri.EscapeDataString(keyValuePair.Value), cookierHeaderValue); | |
position++; | |
} | |
Headers.Append(HeaderNames.SetCookie, cookies); |
I am indeed calling it but just once at the end (L139), I just wanted to avoid calling Headers.Append
multiple times because at the end it calls StringValues.concat
and it means unnecessary allocation of arrays
, hopefully I was able to build the full array of new cookies just like the example you gave me.
aspnetcore/src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs
Lines 160 to 161 in c925f99
var existing = GetHeaderUnmodified(headers, key); | |
SetHeaderUnmodified(headers, key, StringValues.Concat(existing, values)); |
Thanks!
@JuanShares looks like there's some interesting API design questions that this PR would take. Instead of randomizing you, could we discuss this as part of API review meeting and get back to you on a concrete proposal? That way we're not randomizing you too much |
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:
|
Cool! thank you all for the feedback, @pranavkm definitely! I will wait for the concrete proposal for the API in the meanwhile will address other comments :D |
FWIW, I'm not a fan of the |
@barahonajm we think this needs some prep work on our end to allow using default interface implementations. We're going to track this as part of #31723. This PR should be unblocked once that is resolved. @BrennanConroy / @davidfowl could we prioritize that issue to unblock this PR? |
@davidfowl IList is your recommendation to avoid this? |
IReadOnlyList<T> yeah |
@pranavkm I'll work with @barahonajm on this PR in parallel, we should be able to make progress using DIMs and some temporary |
Hmm now I want to try the ReadOnlySpan instead of IReadOnlyLisr |
Well initially I went through the |
Can you humor me and try |
Yes sir, I have tried, and these are the new results (spoiler: nice suggestion!) AppendCookies (~2x win, 2x less allocations)
DeleteCookies (~4x win, ~3x less allocations)
|
This is the new API 😄 |
@Tratcher Sorry for the delay, I finally was able to get back to this, I was thinking on the DIM implementation through these days and came to the conclusion it should be as simple as calling the normal The other option is put a bit more work on this and bring the necessary stuff to the interface for the new method to work, if this is better, I can do it too. aspnetcore/src/Http/Http.Features/src/IResponseCookies.cs Lines 31 to 44 in df9ee2f
FYI @davidfowl @pranavkm |
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.
The functional part of this looks fine, I've added some minor comments. I'll follow up about the PublicAPI cross targeting issues to see if this is the expected pattern. Worst case we get someone to do #31723 first and it becomes moot.
src/Security/perf/Microbenchmarks/ChunkingCookieManagerBenchmark.cs
Outdated
Show resolved
Hide resolved
src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj
Outdated
Show resolved
Hide resolved
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.
From a build perspective but please address https://github.com/dotnet/aspnetcore/pull/31625/files?file-filters%5B%5D=.csproj&file-filters%5B%5D=.txt#r616226305 and get additional approvals before squish / merge.
src/Security/perf/Microbenchmarks/Microsoft.AspNetCore.Security.Microbenchmarks.csproj
Outdated
Show resolved
Hide resolved
src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj
Outdated
Show resolved
Hide resolved
ead98aa
to
ab3e803
Compare
Hello @Tratcher! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
Thanks, that had a lot of build complications to deal with. |
Summary
These changes are to improve the performance for the operation done by ChunkingCookieManager
AppendCookies (~2x win)
DeleteCookies (~4x win)
Details
This PR took as a guidance the original comments done on ticket #31508
1.
DeleteCookie
callsAppendCookie
which is designed for larger chunked cookies. However, when deleting each of the values is an empty string. It could do something much more targeted.It now calls directly a new public api on
ResponseCookies.cs
who makes sure to add a collection of key pair values as cookies to theSet-Cookier
header in an efficient manner2.
DeleteCookie
also could the delegate allocation usingEnumerable.Where
and use a bespoke iterator similar toResponseCookies.Delete
It now deletes the response cookies using a simple
for
loop asResponseCookies
class does3.
AppendResponseCookie
invokesResponseCookies.Append
which allocates aResponseCookieValues
instance and an intermediaryStringValues
for the header. Since we know we're running inside a loop, it could be better optimized for fewer allocationsIt now calls directly a new public api on
ResponseCookies.cs
who makes sure to add a collection of key pair values as cookies to theSet-Cookier
header in an efficient mannerNew API Proposal
As seen on
ChunkingCookieManager
the need to add several cookies at once using the same CookieOptions seems to be common, the new overload will cover this need by receiving aIDictionary<string, string>
whose values will be added to theSet-Cookier
response header in an efficient manner:Addresses #31508
Update 1: Numbers were updated according to the recent changes based on the feedback