Conversation
|
Azure Pipelines: 16 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Suppressed comments (2)
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParserOptions.cs:28
SseParserOptions<T>.MaxBufferSizeis documented as "-1 to use the default limit", but values < -1 currently flow through and (because of the_maxBufferSize >= 0guard) effectively disable the limit. It would be safer to validate thatMaxBufferSizeis either -1 or >= 0 and throwArgumentOutOfRangeExceptionotherwise.
/// <summary>Gets the parser to use to transform each payload of bytes into a data element.</summary>
public SseItemParser<T> ItemParser { get; }
/// <summary>Gets or sets the maximum buffer size, or -1 to use the default limit.</summary>
public int MaxBufferSize { get; set; } = -1;
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:34
- There’s no test coverage for invalid
MaxBufferSizevalues (e.g., < -1). Adding a focused test would ensure the new option can’t be used to silently disable buffer limiting via negative values.
[Fact]
public void Options_DefaultMaxBufferSize()
{
var options = new SseParserOptions<string>(delegate { return ""; });
Assert.Equal(-1, options.MaxBufferSize);
}
|
Tagging subscribers to this area: @karelz, @dotnet/ncl |
Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:996
- This helper decodes payload bytes via bytes.ToArray(), which adds an extra allocation for every parsed event. Encoding.UTF8.GetString(ReadOnlySpan) can be used directly (as in SseParser.Create(Stream)).
private static SseParser<string> CreateParser(Stream stream) =>
CreateParser(stream, static (_, bytes) => Encoding.UTF8.GetString(bytes.ToArray()));
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser.cs:14
- The XML doc summary for the non-generic Create(Stream) overload references SseItem{T}, but this method always returns SseParser. This produces incorrect public docs/intellisense and broken cross-references.
/// <summary>Creates a parser for parsing a <paramref name="sseStream"/> of server-sent events into a sequence of <see cref="SseItem{T}"/> values.</summary>
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:916
- These tests convert the ReadOnlySpan payload to an array before decoding. The product code path already uses Encoding.UTF8.GetString(ReadOnlySpan) directly; using ToArray() here adds per-event allocations and increases test runtime/GC pressure unnecessarily.
This issue also appears on line 995 of the same file.
var options = new SseParserOptions<string>(static (_, bytes) => Encoding.UTF8.GetString(bytes.ToArray()))
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser_1.cs:565
- MaxBufferSize enforcement currently happens only via GrowBuffer(minimumLength). That means the parser can still buffer more than the configured limit without calling GrowBuffer (e.g., initial Rent(1024) when MaxBufferSize < 1024, or when ArrayPool returns a bucket size larger than the requested minimumLength). To honor the contract, the code needs to validate the actual buffered byte counts (e.g., after incrementing _lineLength in FillLineBuffer/FillLineBufferAsync and after appending to _dataLength) against _maxBufferSize, not just the requested growth size.
/// <summary>Grows the buffer, returning the existing one to the ArrayPool and renting an ArrayPool replacement.</summary>
private void GrowBuffer([NotNull] ref byte[]? buffer, int minimumLength)
{
if (_maxBufferSize >= 0 && minimumLength > _maxBufferSize)
{
throw new InvalidDataException(SR.InvalidDataException_SseExceededMaxLength);
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (6)
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser.cs:14
- The XML doc for the non-generic Create(Stream) overload now references SseItem{T}, but this method returns SseParser and the rest of the doc/comment refers to strings. This makes the public docs misleading.
/// <summary>Creates a parser for parsing a <paramref name="sseStream"/> of server-sent events into a sequence of <see cref="SseItem{T}"/> values.</summary>
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser.cs:43
- Adding Create(Stream, SseParserOptions) introduces an overload-resolution ambiguity for callers that previously wrote SseParser.Create(stream, null) to validate argument checking; that call will now be ambiguous between the itemParser and options overloads unless the null is explicitly cast. This is a (small but real) source-compat risk that should be explicitly acknowledged/validated as acceptable.
public static SseParser<T> Create<T>(Stream sseStream, SseParserOptions<T> options)
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:25
- Parse_InvalidArguments_Throws no longer covers the existing Create(Stream) null check, and it also no longer validates that passing a null itemParser to the itemParser overload throws with the expected parameter name (which now requires an explicit cast because of the new options overload). Keeping these checks helps ensure the public surface continues to throw consistent ArgumentNullException parameter names.
[Fact]
public void Parse_InvalidArguments_Throws()
{
AssertExtensions.Throws<ArgumentNullException>("itemParser", () => new SseParserOptions<string>(null));
AssertExtensions.Throws<ArgumentNullException>("sseStream", () => SseParser.Create<string>(null, new SseParserOptions<string>(delegate { return ""; })));
AssertExtensions.Throws<ArgumentNullException>("options", () => SseParser.Create<string>(Stream.Null, (SseParserOptions<string>)null));
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:996
- CreateParser's default string parser allocates via bytes.ToArray() just to decode UTF-8. Using Encoding.UTF8.GetString(ReadOnlySpan) avoids the allocation and aligns with the default parser behavior in SseParser.Create(Stream).
private static SseParser<string> CreateParser(Stream stream) =>
CreateParser(stream, static (_, bytes) => Encoding.UTF8.GetString(bytes.ToArray()));
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser_1.cs:562
- _maxBufferSize is always non-negative (it's either DefaultMaxBufferSize or a validated MaxBufferSize value), so the "_maxBufferSize >= 0" part of this condition is redundant and can be removed to simplify the check.
if (_maxBufferSize >= 0 && minimumLength > _maxBufferSize)
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:916
- The item parser in this test allocates via bytes.ToArray() just to decode UTF-8. Encoding.UTF8.GetString has a ReadOnlySpan overload (and the production code uses it), so this can avoid an allocation and better match real usage.
var options = new SseParserOptions<string>(static (_, bytes) => Encoding.UTF8.GetString(bytes.ToArray()))
Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com>
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (5)
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser.cs:14
- The XML doc for the non-generic Create(Stream) overload references SseItem{T}, but this overload returns SseParser and produces SseItem. This makes the public docs misleading.
This issue also appears on line 34 of the same file.
/// <summary>Creates a parser for parsing a <paramref name="sseStream"/> of server-sent events into a sequence of <see cref="SseItem{String}"/> values.</summary>
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser.cs:35
- Create(Stream, SseItemParser) now constructs SseParserOptions before validating sseStream. This changes ArgumentNullException behavior when both arguments are null (now throws for itemParser instead of sseStream), which is a potentially observable breaking change for invalid inputs. Preserve the prior validation order by checking sseStream first.
public static SseParser<T> Create<T>(Stream sseStream, SseItemParser<T> itemParser) =>
Create(sseStream, new SseParserOptions<T>(itemParser));
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParserOptions.cs:24
- MaxBufferSize's documentation doesn't specify the unit. Since this is a public API and the value is used as a byte count internally, the summary should explicitly say "bytes" to avoid ambiguity.
/// <summary>Gets or sets the maximum buffer size, or -1 to use the default limit.</summary>
/// <exception cref="ArgumentOutOfRangeException">The value set is less than -1.</exception>
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser_1.cs:98
- With MaxBufferSize now configurable, the current line-buffer growth strategy (doubling) and initial ArrayPool rent size can make the limit imprecise or ineffective. For example, if MaxBufferSize is between two growth steps (e.g., 1500), the parser will throw early when trying to grow from 1024→2048, and if MaxBufferSize is smaller than the initial rent size (1024), the parser can buffer more than MaxBufferSize without a growth check. Consider enforcing the limit based on the actual buffered length (_lineLength/_dataLength) after reads/appends, and sizing growth based on required length rather than the next doubling step.
_stream = stream;
_itemParser = options.ItemParser;
_maxBufferSize = options.MaxBufferSize == -1 ? DefaultMaxBufferSize : options.MaxBufferSize;
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:25
- Parse_InvalidArguments_Throws no longer covers argument validation for existing public overloads (e.g., Create(null) and Create(stream, null itemParser)). This reduces coverage for parameter validation behavior that should remain stable alongside the new options overload.
public void Parse_InvalidArguments_Throws()
{
AssertExtensions.Throws<ArgumentNullException>("itemParser", () => new SseParserOptions<string>(null));
AssertExtensions.Throws<ArgumentNullException>("sseStream", () => SseParser.Create<string>(null, new SseParserOptions<string>(delegate { return ""; })));
AssertExtensions.Throws<ArgumentNullException>("options", () => SseParser.Create<string>(Stream.Null, (SseParserOptions<string>)null));
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser.cs:35
Create<T>(Stream, SseItemParser<T>)now constructsSseParserOptions<T>before validatingsseStream, which changes the exception behavior when bothsseStreamanditemParserare null (it will throw foritemParserfirst). For consistency with the other overload and to preserve prior argument-validation order, validatesseStreambefore creating the options object.
public static SseParser<T> Create<T>(Stream sseStream, SseItemParser<T> itemParser) =>
Create(sseStream, new SseParserOptions<T>(itemParser));
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:26
Parse_InvalidArguments_Throwsno longer covers theSseParser.Create(Stream)null-check or theCreate<T>(Stream, SseItemParser<T>)nullitemParsercase. Keeping these assertions helps ensure all public overloads continue to validate arguments and preserve parameter names.
[Fact]
public void Parse_InvalidArguments_Throws()
{
AssertExtensions.Throws<ArgumentNullException>("itemParser", () => new SseParserOptions<string>(null));
AssertExtensions.Throws<ArgumentNullException>("sseStream", () => SseParser.Create<string>(null, new SseParserOptions<string>(delegate { return ""; })));
AssertExtensions.Throws<ArgumentNullException>("options", () => SseParser.Create<string>(Stream.Null, (SseParserOptions<string>)null));
}
| internal SseParser(Stream stream, SseParserOptions<T> options) | ||
| { | ||
| _stream = stream; | ||
| _itemParser = itemParser; | ||
| _itemParser = options.ItemParser; | ||
| _maxBufferSize = options.MaxBufferSize == -1 ? DefaultMaxBufferSize : options.MaxBufferSize; | ||
| } |
| public static System.Net.ServerSentEvents.SseParser<string> Create(System.IO.Stream sseStream) { throw null; } | ||
| public static System.Net.ServerSentEvents.SseParser<T> Create<T>(System.IO.Stream sseStream, System.Net.ServerSentEvents.SseItemParser<T> itemParser) { throw null; } |
There was a problem hiding this comment.
@copilot we can't remove preexisting Create methods. Bring them back.
|
|
||
| memoryStream.Position = 0; | ||
| int count = 0; | ||
| foreach (SseItem<byte[]> item in SseParser.Create(memoryStream, (eventType, data) => data.ToArray()).Enumerate()) |
There was a problem hiding this comment.
Do we need to change it? All existing create methods remained unchanged and there is nothing in the test exercising new limiting capability.
@copilot consier undoing changes in this test file.
There was a problem hiding this comment.
Reverted in 0fe7c83 — this test file is now identical to its pre-PR state.
Note
This reply was generated by GitHub Copilot.
| [Fact] | ||
| public async Task Parse_Sync_SupportsOnlyOneEnumeration_Throws() | ||
| { | ||
| SseParser<string> parser = SseParser.Create(Stream.Null); |
There was a problem hiding this comment.
Similar in this file. No need to wrap SseParser.Create. Test should pass unmodified. Just add new teste testing new Create method and limiting capability. Do not touch preexisting tests.
@copilot please rework.
There was a problem hiding this comment.
Reworked in 0fe7c83 — removed the CreateParser wrapper, all preexisting tests now call SseParser.Create unchanged, and only additive tests/assertions were added for the new options-based API (plus one required disambiguation of a null argument since the new overload makes it ambiguous).
Note
This reply was generated by GitHub Copilot.
Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com>
Addressed in 0fe7c83: reverted unnecessary test wrapping per @mrek-msft's comments, and fixed the Note This reply was generated by GitHub Copilot. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser_1.cs:526
- MaxBufferSize is enforced only after reading into the line buffer. Because Stream.Read is given the full remaining buffer, a single read can pull in more than the configured limit (especially if ArrayPool returns an array larger than requested), and only then throws. Cap the read size to the remaining allowed bytes (and throw when already at the cap) so the limit is enforced before reading past it.
_lineLength += bytesRead;
if (_lineLength > _maxBufferSize)
{
throw new InvalidDataException(SR.InvalidDataException_SseExceededMaxLength);
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParser_1.cs:551
- MaxBufferSize is enforced only after the async read completes. A single ReadAsync can read more than the configured limit (especially if the rented array is larger than requested), and only then throws. Cap the async read size to the remaining allowed bytes (and throw when already at the cap) so the limit is enforced before reading past it.
_lineLength += bytesRead;
if (_lineLength > _maxBufferSize)
{
throw new InvalidDataException(SR.InvalidDataException_SseExceededMaxLength);
src/libraries/System.Net.ServerSentEvents/src/System/Net/ServerSentEvents/SseParserOptions.cs:33
- MaxBufferSize currently allows 0, which can result in renting a 0-length buffer and the parser treating a non-empty stream as EOF without reading (since Stream.Read on an empty span returns 0). Consider rejecting 0 and requiring either -1 (default) or a positive size in bytes.
/// <summary>Gets or sets the maximum buffer size, or -1 to use the default limit.</summary>
/// <exception cref="ArgumentOutOfRangeException">The value set is less than -1.</exception>
public int MaxBufferSize
src/libraries/System.Net.ServerSentEvents/tests/SseParserTests.cs:919
- This test uses
bytes.ToArray()just to decode UTF-8, which adds an avoidable allocation.Encoding.UTF8.GetStringsupports decoding directly from aReadOnlySpan<byte>in this codebase, so the copy isn't needed.
var options = new SseParserOptions<string>(static (_, bytes) => Encoding.UTF8.GetString(bytes.ToArray()))
SSE parsing now supports a caller-configured buffer limit through
SseParserOptions<T>.API
SseParserOptions<T>withItemParserandMaxBufferSize.MaxBufferSizeto-1by default, preserving the internal default limit.SseParser.Createoverload as the parser creation path.Coverage
Note
This description was generated by GitHub Copilot.