-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Enforce HttpClient limits on GetFromJsonAsync #79386
Conversation
Tagging subscribers to this area: @dotnet/ncl Issue DetailsEnforces
|
{ | ||
using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(content, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false); | ||
|
||
var lengthLimitStream = new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize); |
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.
Can we avoid allocating this wrapper stream if MaxResponseContentBufferSize is the default int.MaxValue? Not just about this allocation, but about the increased cost on every individual read operation.
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.
Sure.
Looks like the DeleteAsync
overload doesn't use ResponseHeadersRead
, so we can avoid all of this overhead there as well.
While on the topic, wrapper streams like these are the primary reason why I opened #77935. The per-call allocation overhead disappears with zero-byte reads.
Another similar example is the WebSockets
telemetry in YARP where we intercept a read stream like this, but it adds almost no overhead because YARP does zero-byte reads by default
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.
Looking at this again, I don't think we should avoid this wrapper - we'd still want to prevent the deserialize from reading more than 2 GB in that case.
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 per-call allocation overhead disappears with zero-byte reads.
How so?
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 read in the wrapper stream is implemented like so:
ValueTask<int> readTask = _innerStream.ReadAsync(buffer, cancellationToken);
if (buffer.IsEmpty)
{
return readTask;
}
if (readTask.IsCompletedSuccessfully)
{
int read = readTask.Result;
CheckLengthLimit(read);
return new ValueTask<int>(read);
}
return Core(readTask); // Allocate
Assuming the caller is using zero-byte reads, you will alternate between
- The
buffer.IsEmpty
branch for the zero-byte read where we'll just pass through the task as we're not interested in the result (it won't affect the read limit) - The
readTask.IsCompletedSuccessfully
branch after a zero-byte read as data is available immediately
You'll only hit the allocating path if the underlying stream doesn't support zero-byte reads, or if you hit an exception.
The WebSocket telemetry stream I mentioned is using exactly the same pattern. The result of the zero-byte read doesn't affect us as there isn't any data to parse.
src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs
Outdated
Show resolved
Hide resolved
66f61b3
to
f103729
Compare
Enforces
HttpClient.Timeout
andHttpClient.MaxResponseContentBufferSize
on the whole response (including the content transfer) to matchHttpClient.Get{String/ByteArray}Async
.