From c1e8e581e53e35fe64295cbd743f00341918995d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 5 Sep 2026 09:36:47 +1000 Subject: [PATCH] Add Write(ReadOnlySpan) override to FileBufferingWriteStream Span-based writers such as System.Text.Json previously went through the base Stream fallback, which rents a pooled array and copies before the array overload copies again into the paged buffer. Move the write implementation onto the span overload, have the array overload delegate to it, and give PagedByteBuffer a ReadOnlySpan Add that the ReadOnlyMemory overload forwards to. --- .../WebUtilities/src/FileBufferingWriteStream.cs | 15 +++++++++++---- src/Http/WebUtilities/src/PagedByteBuffer.cs | 11 +++++++---- src/Http/WebUtilities/src/PublicAPI.Unshipped.txt | 1 + 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs index 280f583183ff..cf74b402660f 100644 --- a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs @@ -107,20 +107,27 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken public override void Write(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); + + Write(buffer.AsSpan(offset, count)); + } + + /// + public override void Write(ReadOnlySpan buffer) + { ThrowIfDisposed(); - if (_bufferLimit.HasValue && _bufferLimit - Length < count) + if (_bufferLimit.HasValue && _bufferLimit - Length < buffer.Length) { Dispose(); throw new IOException("Buffer limit exceeded."); } // Allow buffering in memory if we're below the memory threshold once the current buffer is written. - var allowMemoryBuffer = (_memoryThreshold - count) >= PagedByteBuffer.Length; + var allowMemoryBuffer = (_memoryThreshold - buffer.Length) >= PagedByteBuffer.Length; if (allowMemoryBuffer) { // Buffer content in the MemoryStream if it has capacity. - PagedByteBuffer.Add(buffer, offset, count); + PagedByteBuffer.Add(buffer); Debug.Assert(PagedByteBuffer.Length <= _memoryThreshold); } else @@ -132,7 +139,7 @@ public override void Write(byte[] buffer, int offset, int count) // Spool memory content to disk. PagedByteBuffer.MoveTo(FileStream); - FileStream.Write(buffer, offset, count); + FileStream.Write(buffer); } } diff --git a/src/Http/WebUtilities/src/PagedByteBuffer.cs b/src/Http/WebUtilities/src/PagedByteBuffer.cs index a148275a3a4c..b57a75208696 100644 --- a/src/Http/WebUtilities/src/PagedByteBuffer.cs +++ b/src/Http/WebUtilities/src/PagedByteBuffer.cs @@ -44,20 +44,23 @@ public void Add(byte[] buffer, int offset, int count) => Add(buffer.AsMemory(offset, count)); public void Add(ReadOnlyMemory memory) + => Add(memory.Span); + + public void Add(ReadOnlySpan span) { ThrowIfDisposed(); - while (!memory.IsEmpty) + while (!span.IsEmpty) { var currentPage = CurrentPage; - var copyLength = Math.Min(memory.Length, currentPage.Length - _currentPageIndex); + var copyLength = Math.Min(span.Length, currentPage.Length - _currentPageIndex); - memory.Slice(0, copyLength).CopyTo(currentPage.AsMemory(_currentPageIndex, copyLength)); + span.Slice(0, copyLength).CopyTo(currentPage.AsSpan(_currentPageIndex, copyLength)); Length += copyLength; _currentPageIndex += copyLength; - memory = memory.Slice(copyLength); + span = span.Slice(copyLength); } } diff --git a/src/Http/WebUtilities/src/PublicAPI.Unshipped.txt b/src/Http/WebUtilities/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..a5e29bcfc4ab 100644 --- a/src/Http/WebUtilities/src/PublicAPI.Unshipped.txt +++ b/src/Http/WebUtilities/src/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +override Microsoft.AspNetCore.WebUtilities.FileBufferingWriteStream.Write(System.ReadOnlySpan buffer) -> void