diff --git a/src/Http/WebUtilities/src/FileBufferingReadStream.cs b/src/Http/WebUtilities/src/FileBufferingReadStream.cs index e96047a75a22..5a3de14329b5 100644 --- a/src/Http/WebUtilities/src/FileBufferingReadStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingReadStream.cs @@ -313,7 +313,8 @@ public override int Read(Span buffer) { _buffer.Write(buffer.Slice(0, read)); } - else + // Allow zero-byte reads + else if (buffer.Length > 0) { _completelyBuffered = true; } @@ -388,7 +389,8 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation { await _buffer.WriteAsync(buffer.Slice(0, read), cancellationToken); } - else + // Allow zero-byte reads + else if (buffer.Length > 0) { _completelyBuffered = true; } diff --git a/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs b/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs index 26ed3a854e7a..c4bffe076f14 100644 --- a/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs +++ b/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs @@ -36,6 +36,39 @@ public void FileBufferingReadStream_Properties_ExpectedValues() Assert.True(inner.CanSeek); } + [Fact] + public void FileBufferingReadStream_Sync0ByteReadUnderThreshold_DoesntCreateFile() + { + var inner = MakeStream(1024); + using (var stream = new FileBufferingReadStream(inner, 1024 * 2, null, Directory.GetCurrentDirectory())) + { + var bytes = new byte[1000]; + var read0 = stream.Read(bytes, 0, 0); + Assert.Equal(0, read0); + Assert.Equal(read0, stream.Length); + Assert.Equal(read0, stream.Position); + Assert.True(stream.InMemory); + Assert.Null(stream.TempFileName); + + var read1 = stream.Read(bytes, 0, bytes.Length); + Assert.Equal(bytes.Length, read1); + Assert.Equal(read0 + read1, stream.Length); + Assert.Equal(read0 + read1, stream.Position); + Assert.True(stream.InMemory); + Assert.Null(stream.TempFileName); + + var read2 = stream.Read(bytes, 0, bytes.Length); + Assert.Equal(inner.Length - read0 - read1, read2); + Assert.Equal(read0 + read1 + read2, stream.Length); + Assert.Equal(read0 + read1 + read2, stream.Position); + Assert.True(stream.InMemory); + Assert.Null(stream.TempFileName); + + var read3 = stream.Read(bytes, 0, bytes.Length); + Assert.Equal(0, read3); + } + } + [Fact] public void FileBufferingReadStream_SyncReadUnderThreshold_DoesntCreateFile() { @@ -165,6 +198,39 @@ public void FileBufferingReadStream_SyncReadWithOnDiskLimit_EnforcesLimit() /////////////////// + [Fact] + public async Task FileBufferingReadStream_Async0ByteReadUnderThreshold_DoesntCreateFile() + { + var inner = MakeStream(1024); + using (var stream = new FileBufferingReadStream(inner, 1024 * 2, null, Directory.GetCurrentDirectory())) + { + var bytes = new byte[1000]; + var read0 = await stream.ReadAsync(bytes, 0, 0); + Assert.Equal(0, read0); + Assert.Equal(read0, stream.Length); + Assert.Equal(read0, stream.Position); + Assert.True(stream.InMemory); + Assert.Null(stream.TempFileName); + + var read1 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(bytes.Length, read1); + Assert.Equal(read0 + read1, stream.Length); + Assert.Equal(read0 + read1, stream.Position); + Assert.True(stream.InMemory); + Assert.Null(stream.TempFileName); + + var read2 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(inner.Length - read0 - read1, read2); + Assert.Equal(read0 + read1 + read2, stream.Length); + Assert.Equal(read0 + read1 + read2, stream.Position); + Assert.True(stream.InMemory); + Assert.Null(stream.TempFileName); + + var read3 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(0, read3); + } + } + [Fact] public async Task FileBufferingReadStream_AsyncReadUnderThreshold_DoesntCreateFile() { @@ -237,6 +303,47 @@ public async Task FileBufferingReadStream_AsyncReadOverThreshold_CreatesFile() Assert.False(File.Exists(tempFileName)); } + [Fact] + public async Task FileBufferingReadStream_Async0ByteReadAfterBuffering_ReadsFromFile() + { + var inner = MakeStream(1024 * 2); + string tempFileName; + using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory())) + { + await stream.DrainAsync(default); + stream.Position = 0; + Assert.Equal(inner.Length, stream.Length); + Assert.Equal(0, stream.Position); + Assert.False(stream.InMemory); + Assert.NotNull(stream.TempFileName); + tempFileName = stream.TempFileName!; + Assert.True(File.Exists(tempFileName)); + + var bytes = new byte[1000]; + var read0 = await stream.ReadAsync(bytes, 0, 0); + Assert.Equal(0, read0); + Assert.Equal(read0, stream.Position); + + var read1 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(bytes.Length, read1); + Assert.Equal(read0 + read1, stream.Position); + + var read2 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(bytes.Length, read2); + Assert.Equal(read0 + read1 + read2, stream.Position); + + var read3 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(inner.Length - read0 - read1 - read2, read3); + Assert.Equal(read0 + read1 + read2 + read3, stream.Length); + Assert.Equal(read0 + read1 + read2 + read3, stream.Position); + + var read4 = await stream.ReadAsync(bytes, 0, bytes.Length); + Assert.Equal(0, read4); + } + + Assert.False(File.Exists(tempFileName)); + } + [Fact] public async Task FileBufferingReadStream_AsyncReadWithInMemoryLimit_EnforcesLimit() {