Skip to content

Commit ed5034e

Browse files
authored
Actually fix the CopyToAsync this time (#24609)
* Actually fix the CopyToAsync this time - Hard code the 4K buffer length for CopyToAsync - Write a test this time :D
1 parent a28c2b6 commit ed5034e

File tree

2 files changed

+39
-8
lines changed

2 files changed

+39
-8
lines changed

src/Http/WebUtilities/src/FileBufferingReadStream.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,11 @@ public override void Flush()
363363

364364
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
365365
{
366+
// Set a minimum buffer size of 4K since the base Stream implementation has weird behavior when the stream is
367+
// seekable *and* the length is 0 (it passes in a buffer size of 1).
368+
// See https://github.com/dotnet/runtime/blob/222415c56c9ea73530444768c0e68413eb374f5d/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs#L164-L184
369+
bufferSize = Math.Max(4096, bufferSize);
370+
366371
// If we're completed buffered then copy from the underlying source
367372
if (_completelyBuffered)
368373
{
@@ -372,7 +377,7 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio
372377
async Task CopyToAsyncImpl()
373378
{
374379
// At least a 4K buffer
375-
byte[] buffer = _bytePool.Rent(Math.Min(bufferSize, 4096));
380+
byte[] buffer = _bytePool.Rent(bufferSize);
376381
try
377382
{
378383
while (true)

src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Buffers;
66
using System.IO;
77
using System.Linq;
8-
using System.Text;
98
using System.Threading.Tasks;
109
using Moq;
1110
using Xunit;
@@ -353,39 +352,49 @@ public async Task FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRente
353352
[Fact]
354353
public async Task CopyToAsyncWorks()
355354
{
356-
var data = Enumerable.Range(0, 1024).Select(b => (byte)b).Reverse().ToArray();
355+
// 4K is the lower bound on buffer sizes
356+
var bufferSize = 4096;
357+
var mostExpectedWrites = 8;
358+
var data = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).ToArray();
357359
var inner = new MemoryStream(data);
358360

359361
using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
360362

361-
var withoutBufferMs = new MemoryStream();
363+
var withoutBufferMs = new NumberOfWritesMemoryStream();
362364
await stream.CopyToAsync(withoutBufferMs);
363365

364-
var withBufferMs = new MemoryStream();
366+
var withBufferMs = new NumberOfWritesMemoryStream();
365367
stream.Position = 0;
366368
await stream.CopyToAsync(withBufferMs);
367369

368370
Assert.Equal(data, withoutBufferMs.ToArray());
371+
Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
369372
Assert.Equal(data, withBufferMs.ToArray());
373+
Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
370374
}
371375

372376
[Fact]
373377
public async Task CopyToAsyncWorksWithFileThreshold()
374378
{
375-
var data = Enumerable.Range(0, 1024).Select(b => (byte)b).Reverse().ToArray();
379+
// 4K is the lower bound on buffer sizes
380+
var bufferSize = 4096;
381+
var mostExpectedWrites = 8;
382+
var data = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).Reverse().ToArray();
376383
var inner = new MemoryStream(data);
377384

378385
using var stream = new FileBufferingReadStream(inner, 100, bufferLimit: null, GetCurrentDirectory());
379386

380-
var withoutBufferMs = new MemoryStream();
387+
var withoutBufferMs = new NumberOfWritesMemoryStream();
381388
await stream.CopyToAsync(withoutBufferMs);
382389

383-
var withBufferMs = new MemoryStream();
390+
var withBufferMs = new NumberOfWritesMemoryStream();
384391
stream.Position = 0;
385392
await stream.CopyToAsync(withBufferMs);
386393

387394
Assert.Equal(data, withoutBufferMs.ToArray());
395+
Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
388396
Assert.Equal(data, withBufferMs.ToArray());
397+
Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
389398
}
390399

391400
[Fact]
@@ -486,5 +495,22 @@ private static string GetCurrentDirectory()
486495
{
487496
return AppContext.BaseDirectory;
488497
}
498+
499+
private class NumberOfWritesMemoryStream : MemoryStream
500+
{
501+
public int NumberOfWrites { get; set; }
502+
503+
public override void Write(byte[] buffer, int offset, int count)
504+
{
505+
NumberOfWrites++;
506+
base.Write(buffer, offset, count);
507+
}
508+
509+
public override void Write(ReadOnlySpan<byte> source)
510+
{
511+
NumberOfWrites++;
512+
base.Write(source);
513+
}
514+
}
489515
}
490516
}

0 commit comments

Comments
 (0)