Skip to content

Commit 57d758c

Browse files
committed
Fixed #256
1 parent 54bb3c3 commit 57d758c

File tree

3 files changed

+115
-28
lines changed

3 files changed

+115
-28
lines changed

src/DotNext.IO/DotNext.IO.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<Authors>.NET Foundation and Contributors</Authors>
1212
<Company />
1313
<Product>.NEXT Family of Libraries</Product>
14-
<VersionPrefix>5.18.0</VersionPrefix>
14+
<VersionPrefix>5.18.1</VersionPrefix>
1515
<VersionSuffix></VersionSuffix>
1616
<AssemblyName>DotNext.IO</AssemblyName>
1717
<PackageLicenseExpression>MIT</PackageLicenseExpression>

src/DotNext.IO/IO/PoolingBufferedStream.cs

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public override int WriteTimeout
9494
}
9595

9696
/// <inheritdoc/>
97+
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
9798
public override long Length
9899
{
99100
get
@@ -156,6 +157,7 @@ private void EnsureReadBufferIsEmpty()
156157
}
157158

158159
/// <inheritdoc/>
160+
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
159161
Memory<byte> IBufferedWriter.Buffer
160162
{
161163
get
@@ -302,23 +304,29 @@ private void WriteCore(ReadOnlySpan<byte> data)
302304

303305
if (writePosition is 0)
304306
ClearReadBufferBeforeWrite();
305-
307+
306308
var freeBuf = EnsureBufferAllocated().Span.Slice(writePosition);
307309

308-
// drain buffered data if needed
309-
if (freeBuf.Length < data.Length)
310-
WriteCore();
311-
312-
// if internal buffer has not enough space then just write through
313-
if (data.Length > freeBuf.Length)
310+
if (data.Length <= freeBuf.Length)
314311
{
315-
stream.Write(data);
316-
Reset();
312+
data.CopyTo(freeBuf);
313+
writePosition += data.Length;
317314
}
318-
else
315+
else if (data.Length < maxBufferSize)
319316
{
317+
data.CopyTo(freeBuf, out var bytesWritten);
318+
stream.Write(freeBuf = buffer.Span);
319+
data = data.Slice(bytesWritten);
320320
data.CopyTo(freeBuf);
321-
writePosition += data.Length;
321+
writePosition = data.Length;
322+
323+
Debug.Assert(writePosition > 0);
324+
}
325+
else
326+
{
327+
WriteCore();
328+
stream.Write(data);
329+
Reset();
322330
}
323331
}
324332

@@ -360,35 +368,64 @@ public override ValueTask WriteAsync(ReadOnlyMemory<byte> data, CancellationToke
360368
return task;
361369
}
362370

363-
private async ValueTask WriteCoreAsync(ReadOnlyMemory<byte> data, CancellationToken token)
371+
private ValueTask WriteCoreAsync(ReadOnlyMemory<byte> data, CancellationToken token)
364372
{
365373
Debug.Assert(stream is not null);
366374

367375
if (writePosition is 0)
368376
ClearReadBufferBeforeWrite();
369377

370-
var freeBuf = EnsureBufferAllocated().Memory.Slice(writePosition);
371-
372-
// drain buffered data if needed
373-
if (freeBuf.Length < data.Length)
378+
var freeCapacity = maxBufferSize - writePosition;
379+
380+
ValueTask task;
381+
if (data.Length <= freeCapacity)
374382
{
375-
await WriteCoreAsync(out _, token).ConfigureAwait(false);
376-
freeBuf = buffer.Memory.Slice(writePosition);
383+
data.CopyTo(EnsureBufferAllocated().Memory.Slice(writePosition));
384+
writePosition += data.Length;
385+
task = ValueTask.CompletedTask;
377386
}
378-
379-
// if internal buffer has not enough space then just write through
380-
if (data.Length > freeBuf.Length)
387+
else if (data.Length < maxBufferSize)
381388
{
382-
await stream.WriteAsync(data, token).ConfigureAwait(false);
383-
Reset();
389+
task = CopyAndWriteAsync(data, token);
390+
}
391+
else if (writePosition is 0)
392+
{
393+
task = stream.WriteAsync(data, token);
384394
}
385395
else
386396
{
387-
data.CopyTo(freeBuf);
388-
writePosition += data.Length;
397+
task = WriteWithBufferAsync(data, token);
389398
}
399+
400+
return task;
390401
}
391-
402+
403+
private async ValueTask CopyAndWriteAsync(ReadOnlyMemory<byte> data, CancellationToken token)
404+
{
405+
Debug.Assert(stream is not null);
406+
Debug.Assert(data.Length < maxBufferSize);
407+
408+
var writeBuffer = buffer.Memory;
409+
data.Span.CopyTo(writeBuffer.Span.Slice(writePosition), out var bytesWritten);
410+
await stream.WriteAsync(writeBuffer, token).ConfigureAwait(false);
411+
data = data.Slice(bytesWritten);
412+
data.CopyTo(writeBuffer);
413+
writePosition = data.Length;
414+
415+
Debug.Assert(writePosition > 0);
416+
}
417+
418+
private async ValueTask WriteWithBufferAsync(ReadOnlyMemory<byte> data, CancellationToken token)
419+
{
420+
Debug.Assert(stream is not null);
421+
Debug.Assert(data.Length >= maxBufferSize);
422+
Debug.Assert(writePosition > 0);
423+
424+
await stream.WriteAsync(WrittenMemory, token).ConfigureAwait(false);
425+
await stream.WriteAsync(data, token).ConfigureAwait(false);
426+
Reset();
427+
}
428+
392429
/// <inheritdoc/>
393430
public override Task WriteAsync(byte[] data, int offset, int count, CancellationToken token)
394431
=> WriteAsync(new ReadOnlyMemory<byte>(data, offset, count), token).AsTask();
@@ -454,12 +491,13 @@ private int ReadCore(Span<byte> data)
454491
{
455492
// nothing to do
456493
}
457-
else if (data.Length > MaxBufferSize)
494+
else if (data.Length > maxBufferSize)
458495
{
459496
bytesRead += stream.Read(data);
460497
}
461498
else
462499
{
500+
readPosition = 0;
463501
readLength = stream.Read(EnsureBufferAllocated().Span);
464502
bytesRead += ReadFromBuffer(data);
465503
}
@@ -536,6 +574,7 @@ private async ValueTask<int> ReadCoreAsync(Memory<byte> data, CancellationToken
536574
else
537575
{
538576
Debug.Assert(readPosition == readLength);
577+
readPosition = 0;
539578
readLength = await stream.ReadAsync(EnsureBufferAllocated().Memory, token).ConfigureAwait(false);
540579
bytesRead += ReadFromBuffer(data.Span);
541580
}
@@ -740,6 +779,7 @@ private void EnsureWriteBufferIsEmpty()
740779
}
741780

742781
/// <inheritdoc/>
782+
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
743783
ReadOnlyMemory<byte> IBufferedReader.Buffer
744784
{
745785
get

src/DotNext.Tests/IO/PoolingBufferedStreamTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,4 +389,51 @@ public static void OverwriteStream()
389389
buffered.Write("text"u8);
390390
Equal(4L, buffered.Position);
391391
}
392+
393+
[Fact]
394+
public static async Task RegressionIssue256()
395+
{
396+
const int dataSize = 128 + 3105 + 66 + 3111 + 66 + 3105 + 66 + 2513 + 128;
397+
ReadOnlyMemory<byte> expected = RandomBytes(dataSize);
398+
await using var ms = new MemoryStream();
399+
400+
await using (var buffered = new PoolingBufferedStream(ms, leaveOpen: true) { MaxBufferSize = 8192 })
401+
{
402+
await buffered.WriteAsync(expected);
403+
await buffered.FlushAsync();
404+
}
405+
406+
ms.Position = 0;
407+
await using (var reader = new PoolingBufferedStream(ms, leaveOpen: true) { MaxBufferSize = 4096 })
408+
{
409+
Memory<byte> buffer = new byte[dataSize];
410+
await reader.ReadExactlyAsync(buffer.Slice(0, 3175));
411+
reader.Position = 3303;
412+
await reader.ReadExactlyAsync(buffer.Slice(0, 3107));
413+
Equal(expected.Slice(3303, 3107), buffer.Slice(0, 3107));
414+
}
415+
}
416+
417+
[Fact]
418+
public static void RepeatableReads()
419+
{
420+
var bytes = RandomBytes(128);
421+
using var reader = new PoolingBufferedStream(new MemoryStream(bytes)) { MaxBufferSize = 256 };
422+
True(reader.Read());
423+
False(reader.Read());
424+
True(reader.HasBufferedDataToRead);
425+
426+
Equal(bytes, reader.As<IBufferedReader>().Buffer);
427+
}
428+
429+
[Fact]
430+
public static void ReadEmpty()
431+
{
432+
var bytes = RandomBytes(128);
433+
using var reader = new PoolingBufferedStream(new MemoryStream(bytes)) { MaxBufferSize = 256 };
434+
True(reader.Read());
435+
reader.ReadExactly(new byte[bytes.Length]);
436+
False(reader.Read());
437+
False(reader.HasBufferedDataToRead);
438+
}
392439
}

0 commit comments

Comments
 (0)