From a5eab0ec5deff3816c4ef74fd40331a4c80cf618 Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Sat, 28 Jun 2025 17:21:52 +0200 Subject: [PATCH 1/3] cleanup memory after UnflushedBytes_HandlesLargeValues test --- .../tests/PipeWriterTests.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs index bc082b920cfb90..73af7d029d67aa 100644 --- a/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs +++ b/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -361,19 +361,29 @@ public async Task GetMemoryFlushWithACompletedReaderNoops() Assert.Equal(0, Pipe.Writer.UnflushedBytes); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser), nameof(PlatformDetection.Is64BitProcess))] - public void UnflushedBytes_HandlesLargeValues() + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.Is64BitProcess))] + public async Task UnflushedBytes_HandlesLargeValues() { PipeWriter writer = PipeWriter.Create(Stream.Null); - int bufferSize = 10000; - while (writer.UnflushedBytes >= 0 && writer.UnflushedBytes <= int.MaxValue) + try { - writer.GetMemory(bufferSize); - writer.Advance(bufferSize); - } + int bufferSize = 10000; + + while (writer.UnflushedBytes >= 0 && writer.UnflushedBytes <= int.MaxValue) + { + writer.GetMemory(bufferSize); + writer.Advance(bufferSize); + } - Assert.Equal(2147490000, writer.UnflushedBytes); + Assert.Equal(2147490000, writer.UnflushedBytes); + } + finally + { + await writer.FlushAsync(); + await writer.CompleteAsync(); + GC.Collect(2, GCCollectionMode.Forced, true, true); + } } } } From e6ec6ce2d045a9a370bae1d73cf373a2c7b5b666 Mon Sep 17 00:00:00 2001 From: Badre BSAILA <54767641+pedrobsaila@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:08:19 +0200 Subject: [PATCH 2/3] Update src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs index 73af7d029d67aa..8fdbfb48581dca 100644 --- a/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs +++ b/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -362,6 +362,7 @@ public async Task GetMemoryFlushWithACompletedReaderNoops() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.Is64BitProcess))] + [OuterLoop] public async Task UnflushedBytes_HandlesLargeValues() { PipeWriter writer = PipeWriter.Create(Stream.Null); From 8a0280cc4ef352c0c9576e19b3981d2a9f7b43d4 Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Sun, 13 Jul 2025 15:39:34 +0200 Subject: [PATCH 3/3] use memory pool that manages single buffer and reuse it each time a buffer is requested --- .../tests/PipeWriterTests.cs | 89 ++++++++++++++++--- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs index 8fdbfb48581dca..4b7db2f249e5a5 100644 --- a/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs +++ b/src/libraries/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -361,15 +361,13 @@ public async Task GetMemoryFlushWithACompletedReaderNoops() Assert.Equal(0, Pipe.Writer.UnflushedBytes); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.Is64BitProcess))] - [OuterLoop] - public async Task UnflushedBytes_HandlesLargeValues() + [Fact] + public void UnflushedBytes_HandlesLargeValues() { - PipeWriter writer = PipeWriter.Create(Stream.Null); - - try + int bufferSize = 10000; + using (SingleBufferMemoryPool pool = new(bufferSize)) { - int bufferSize = 10000; + PipeWriter writer = PipeWriter.Create(Stream.Null, new StreamPipeWriterOptions(pool)); while (writer.UnflushedBytes >= 0 && writer.UnflushedBytes <= int.MaxValue) { @@ -379,11 +377,80 @@ public async Task UnflushedBytes_HandlesLargeValues() Assert.Equal(2147490000, writer.UnflushedBytes); } - finally + } + + internal class SingleBufferMemoryPool : MemoryPool + { + private readonly int _maxBufferSize; + private byte[] _buffer; + private bool _disposed; + + internal SingleBufferMemoryPool(int maxBufferSize) + { + _maxBufferSize = maxBufferSize; + _buffer = new byte[maxBufferSize]; + } + + public override int MaxBufferSize => _maxBufferSize; + + public override IMemoryOwner Rent(int minBufferSize = -1) { - await writer.FlushAsync(); - await writer.CompleteAsync(); - GC.Collect(2, GCCollectionMode.Forced, true, true); + if (minBufferSize > _maxBufferSize) + { + throw new ArgumentOutOfRangeException(nameof(minBufferSize), $"{nameof(minBufferSize)} should be less than {_maxBufferSize}"); + } + + return new BufferMemoryOwner(minBufferSize, _maxBufferSize, _buffer); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + return; + } + + if (disposing) + { + _buffer = null; + } + + _disposed = true; + } + } + + internal class BufferMemoryOwner : MemoryManager + { + private int _minBufferSize; + private byte[] _buffer; + private bool _disposed; + + public BufferMemoryOwner(int minBufferSize, int maxBufferSize, byte[] buffer) + { + _minBufferSize = minBufferSize <= 0 ? maxBufferSize : minBufferSize; + _buffer = buffer; + } + + public override Span GetSpan() => new(_buffer, 0, _minBufferSize); + + public override Memory Memory => new(_buffer, 0, _minBufferSize); + + public override MemoryHandle Pin(int elementIndex = 0) => throw new NotImplementedException(); + public override void Unpin() => throw new NotImplementedException(); + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + return; + } + + if (disposing) + { + _buffer = null; + } + + _disposed = true; } } }