Repository navigation
Conversation
When the CRLF-terminated line is already fully buffered and within the length limit, decode it directly from the buffer instead of copying it into a new MemoryStream(200). Otherwise fall back to the existing path, which also produces the limit exception, so results and errors are unchanged. This is the path MultipartReader uses for every boundary remainder and section header line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new tests leave the read buffer empty, bypassing the changed over-limit fallback and potentially missing an off-by-one regression; prime the buffer before calling ReadLine.
1 open issue
What changed in this PR
This PR reduces per-line allocations in WebUtilities’ BufferedReadStream, improving multipart parsing when complete lines are already buffered.
Changes:
- Adds a shared fast path for synchronous and asynchronous reads that decodes buffered CRLF-terminated lines directly.
- Retains the accumulating path for incomplete or over-limit lines.
- Adds tests for buffered lines, UTF-8 content, remaining data, and length limits.
| File | Description |
|---|---|
src/Http/WebUtilities/test/BufferedReadStreamTests.cs |
Adds synchronous and asynchronous line-reading coverage. |
src/Http/WebUtilities/src/BufferedReadStream.cs |
Avoids MemoryStream allocation for complete buffered lines within the limit. |
Review effort: Balanced
Ensures the new tests exercise the buffered fast path, including the over-limit fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Heads-up for reviewers: this PR overlaps with #70156, which was opened earlier from the same account and applies the same 🤖 Posted by ASP.NET Core Perf 2 (AI Agent) |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Retriggered CI with an empty commit (d22cf7f). The previous run had one failure, 🤖 Posted by ASP.NET Core Perf 2 (AI Agent) |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Retriggered CI with an empty commit (de3cd24). The previous run failed only the Blazor E2E test 🤖 Posted by ASP.NET Core Perf 2 (AI Agent) |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Pushed an empty commit (782c1d0) to retrigger CI. Note that its commit message is out of date: the actual failure this run was not a Blazor flake. Evidence that the aspnetcore-ci failure on de3cd24 is unrelated:
🤖 Posted by ASP.NET Core Perf 2 (AI Agent) |
|
aspnetcore-ci is fully green on this run. The only red leg is 🤖 Posted by ASP.NET Core Perf 2 (AI Agent) |

BufferedReadStream.ReadLine/ReadLineAsyncallocated aMemoryStream(200)(object + 200-byte buffer) for every line, even when the whole line was already in the read buffer.MultipartReadercalls it for every boundary remainder and every section header line (including the blank terminator line), so a multipart form pays this several times per section.This adds a fast path: if the buffered bytes contain
\r\nand the line (content + CR) is withinlengthLimit, the line is decoded straight from the buffer with the sameEncoding.UTF8.GetString. Otherwise (no CRLF buffered yet, or the limit would be exceeded) it falls back to the existing accumulating path, so returned strings, the limit exception, and stream position behaviour are unchanged.Benchmark
MultipartReaderover a 3-section form (28-char boundary, 5 header lines, small bodies, bodies drained), BenchmarkDotNet InProcess, 3 interleaved rounds:~24% faster, 29% less allocated.
Tests
Added
BufferedReadStreamTestscovering several buffered lines (empty line, bare\r/\ninside a line,\r\r\n, non-ASCII, trailing unterminated data,BufferedDataremainder), and a buffered line exactly at / just over the length limit, for both sync and async. They pass on main and on this branch. WebUtilities.Tests (512) and Http.Tests pass.