Use .NET Core SequenceReader

Remove BufferReader and use SequenceReader<T> which now ships in CoreFX.

This is related to https://github.com/aspnet/KestrelHttpServer/pull/3068 which builds on the functionality added to the reader.
This commit is contained in:
Jeremy Kuhne 2019-01-24 15:57:18 -08:00
parent d1aa53721b
commit 2c649b2409
4 changed files with 19 additions and 474 deletions

View File

@ -1,153 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;
namespace System.Buffers
{
internal ref struct BufferReader
{
private ReadOnlySpan<byte> _currentSpan;
private int _index;
private ReadOnlySequence<byte> _sequence;
private SequencePosition _currentSequencePosition;
private SequencePosition _nextSequencePosition;
private int _consumedBytes;
private bool _end;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferReader(in ReadOnlySequence<byte> buffer)
{
_index = 0;
_consumedBytes = 0;
_sequence = buffer;
_currentSequencePosition = _sequence.Start;
_nextSequencePosition = _currentSequencePosition;
if (_sequence.TryGet(ref _nextSequencePosition, out var memory, true))
{
_end = false;
_currentSpan = memory.Span;
if (_currentSpan.Length == 0)
{
// No space in first span, move to one with space
MoveNext();
}
}
else
{
// No space in any spans and at end of sequence
_end = true;
_currentSpan = default;
}
}
public bool End => _end;
public int CurrentSegmentIndex => _index;
public SequencePosition Position => _sequence.GetPosition(_index, _currentSequencePosition);
public ReadOnlySpan<byte> CurrentSegment => _currentSpan;
public ReadOnlySpan<byte> UnreadSegment => _currentSpan.Slice(_index);
public int ConsumedBytes => _consumedBytes;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Peek()
{
if (_end)
{
return -1;
}
return _currentSpan[_index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Read()
{
if (_end)
{
return -1;
}
var value = _currentSpan[_index];
_index++;
_consumedBytes++;
if (_index >= _currentSpan.Length)
{
MoveNext();
}
return value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void MoveNext()
{
var previous = _nextSequencePosition;
while (_sequence.TryGet(ref _nextSequencePosition, out var memory, true))
{
_currentSequencePosition = previous;
_currentSpan = memory.Span;
_index = 0;
if (_currentSpan.Length > 0)
{
return;
}
}
_end = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int byteCount)
{
if (!_end && byteCount > 0 && (_index + byteCount) < _currentSpan.Length)
{
_consumedBytes += byteCount;
_index += byteCount;
}
else
{
AdvanceNext(byteCount);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void AdvanceNext(int byteCount)
{
if (byteCount < 0)
{
BuffersThrowHelper.ThrowArgumentOutOfRangeException(BuffersThrowHelper.ExceptionArgument.length);
}
_consumedBytes += byteCount;
while (!_end && byteCount > 0)
{
if ((_index + byteCount) < _currentSpan.Length)
{
_index += byteCount;
byteCount = 0;
break;
}
var remaining = (_currentSpan.Length - _index);
_index += remaining;
byteCount -= remaining;
MoveNext();
}
if (byteCount > 0)
{
BuffersThrowHelper.ThrowArgumentOutOfRangeException(BuffersThrowHelper.ExceptionArgument.length);
}
}
}
}

View File

@ -528,11 +528,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
consumed = buffer.Start;
examined = buffer.Start;
var reader = new BufferReader(buffer);
var ch1 = reader.Read();
var ch2 = reader.Read();
var reader = new SequenceReader<byte>(buffer);
if (ch1 == -1 || ch2 == -1)
if (!reader.TryRead(out var ch1) || !reader.TryRead(out var ch2))
{
examined = reader.Position;
return;
@ -541,21 +539,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
var chunkSize = CalculateChunkSize(ch1, 0);
ch1 = ch2;
while (reader.ConsumedBytes < MaxChunkPrefixBytes)
while (reader.Consumed < MaxChunkPrefixBytes)
{
if (ch1 == ';')
{
consumed = reader.Position;
examined = reader.Position;
AddAndCheckConsumedBytes(reader.ConsumedBytes);
AddAndCheckConsumedBytes(reader.Consumed);
_inputLength = chunkSize;
_mode = Mode.Extension;
return;
}
ch2 = reader.Read();
if (ch2 == -1)
if (!reader.TryRead(out ch2))
{
examined = reader.Position;
return;
@ -566,7 +563,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
consumed = reader.Position;
examined = reader.Position;
AddAndCheckConsumedBytes(reader.ConsumedBytes);
AddAndCheckConsumedBytes(reader.Consumed);
_inputLength = chunkSize;
_mode = chunkSize > 0 ? Mode.Data : Mode.Trailer;
return;

View File

@ -182,25 +182,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
var bufferEnd = buffer.End;
var reader = new BufferReader(buffer);
var start = default(BufferReader);
var reader = new SequenceReader<byte>(buffer);
var start = default(SequenceReader<byte>);
var done = false;
try
{
while (!reader.End)
{
var span = reader.CurrentSegment;
var remaining = span.Length - reader.CurrentSegmentIndex;
var span = reader.CurrentSpan;
var remaining = span.Length - reader.CurrentSpanIndex;
fixed (byte* pBuffer = span)
{
while (remaining > 0)
{
var index = reader.CurrentSegmentIndex;
int ch1;
int ch2;
var index = reader.CurrentSpanIndex;
byte ch1;
byte ch2;
var readAhead = false;
bool readSecond = true;
// Fast path, we're still looking at the same span
if (remaining >= 2)
@ -215,8 +216,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
start = reader;
// Possibly split across spans
ch1 = reader.Read();
ch2 = reader.Read();
reader.TryRead(out ch1);
readSecond = reader.TryRead(out ch2);
readAhead = true;
}
@ -224,7 +225,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (ch1 == ByteCR)
{
// Check for final CRLF.
if (ch2 == -1)
if (!readSecond)
{
// Reset the reader so we don't consume anything
reader = start;
@ -252,7 +253,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (readAhead)
{
reader = start;
index = reader.CurrentSegmentIndex;
index = reader.CurrentSpanIndex;
}
var endIndex = new Span<byte>(pBuffer + index, remaining).IndexOf(ByteLF);
@ -308,7 +309,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
finally
{
consumed = reader.Position;
consumedBytes = reader.ConsumedBytes;
consumedBytes = (int)reader.Consumed;
if (done)
{

View File

@ -1,300 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Xunit;
namespace System.Buffers.Tests
{
public abstract class ReadableBufferReaderFacts
{
public class Array : SingleSegment
{
public Array() : base(ReadOnlySequenceFactory.ArrayFactory) { }
internal Array(ReadOnlySequenceFactory factory) : base(factory) { }
}
public class OwnedMemory : SingleSegment
{
public OwnedMemory() : base(ReadOnlySequenceFactory.OwnedMemoryFactory) { }
}
public class Memory : SingleSegment
{
public Memory() : base(ReadOnlySequenceFactory.MemoryFactory) { }
}
public class SingleSegment : SegmentPerByte
{
public SingleSegment() : base(ReadOnlySequenceFactory.SingleSegmentFactory) { }
internal SingleSegment(ReadOnlySequenceFactory factory) : base(factory) { }
[Fact]
public void AdvanceSingleBufferSkipsBytes()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5 }));
reader.Advance(2);
Assert.Equal(2, reader.CurrentSegmentIndex);
Assert.Equal(3, reader.CurrentSegment[reader.CurrentSegmentIndex]);
Assert.Equal(3, reader.Peek());
reader.Advance(2);
Assert.Equal(5, reader.Peek());
Assert.Equal(4, reader.CurrentSegmentIndex);
Assert.Equal(5, reader.CurrentSegment[reader.CurrentSegmentIndex]);
}
[Fact]
public void TakeReturnsByteAndMoves()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 }));
Assert.Equal(0, reader.CurrentSegmentIndex);
Assert.Equal(1, reader.CurrentSegment[reader.CurrentSegmentIndex]);
Assert.Equal(1, reader.Read());
Assert.Equal(1, reader.CurrentSegmentIndex);
Assert.Equal(2, reader.CurrentSegment[reader.CurrentSegmentIndex]);
Assert.Equal(2, reader.Read());
Assert.Equal(-1, reader.Read());
}
}
public class SegmentPerByte : ReadableBufferReaderFacts
{
public SegmentPerByte() : base(ReadOnlySequenceFactory.SegmentPerByteFactory) { }
internal SegmentPerByte(ReadOnlySequenceFactory factory) : base(factory) { }
}
internal ReadOnlySequenceFactory Factory { get; }
internal ReadableBufferReaderFacts(ReadOnlySequenceFactory factory)
{
Factory = factory;
}
[Fact]
public void PeekReturnsByteWithoutMoving()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 }));
Assert.Equal(1, reader.Peek());
Assert.Equal(1, reader.Peek());
}
[Fact]
public void CursorIsCorrectAtEnd()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 }));
reader.Read();
reader.Read();
Assert.True(reader.End);
}
[Fact]
public void CursorIsCorrectWithEmptyLastBlock()
{
var first = new BufferSegment(new byte[] { 1, 2 });
var last = first.Append(new byte[4]);
var reader = new BufferReader(new ReadOnlySequence<byte>(first, 0, last, 0));
reader.Read();
reader.Read();
reader.Read();
Assert.Same(last, reader.Position.GetObject());
Assert.Equal(0, reader.Position.GetInteger());
Assert.True(reader.End);
}
[Fact]
public void PeekReturnsMinusOneByteInTheEnd()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 }));
Assert.Equal(1, reader.Read());
Assert.Equal(2, reader.Read());
Assert.Equal(-1, reader.Peek());
}
[Fact]
public void AdvanceToEndThenPeekReturnsMinusOne()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5 }));
reader.Advance(5);
Assert.True(reader.End);
Assert.Equal(-1, reader.Peek());
}
[Fact]
public void AdvancingPastLengthThrows()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5 }));
try
{
reader.Advance(6);
Assert.True(false);
}
catch (Exception ex)
{
Assert.True(ex is ArgumentOutOfRangeException);
}
}
[Fact]
public void CtorFindsFirstNonEmptySegment()
{
var buffer = Factory.CreateWithContent(new byte[] { 1 });
var reader = new BufferReader(buffer);
Assert.Equal(1, reader.Peek());
}
[Fact]
public void EmptySegmentsAreSkippedOnMoveNext()
{
var buffer = Factory.CreateWithContent(new byte[] { 1, 2 });
var reader = new BufferReader(buffer);
Assert.Equal(1, reader.Peek());
reader.Advance(1);
Assert.Equal(2, reader.Peek());
}
[Fact]
public void PeekGoesToEndIfAllEmptySegments()
{
var buffer = Factory.CreateOfSize(0);
var reader = new BufferReader(buffer);
Assert.Equal(-1, reader.Peek());
Assert.True(reader.End);
}
[Fact]
public void AdvanceTraversesSegments()
{
var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3 });
var reader = new BufferReader(buffer);
reader.Advance(2);
Assert.Equal(3, reader.CurrentSegment[reader.CurrentSegmentIndex]);
Assert.Equal(3, reader.Read());
}
[Fact]
public void AdvanceThrowsPastLengthMultipleSegments()
{
var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3 });
var reader = new BufferReader(buffer);
try
{
reader.Advance(4);
Assert.True(false);
}
catch (Exception ex)
{
Assert.True(ex is ArgumentOutOfRangeException);
}
}
[Fact]
public void TakeTraversesSegments()
{
var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3 });
var reader = new BufferReader(buffer);
Assert.Equal(1, reader.Read());
Assert.Equal(2, reader.Read());
Assert.Equal(3, reader.Read());
Assert.Equal(-1, reader.Read());
}
[Fact]
public void PeekTraversesSegments()
{
var buffer = Factory.CreateWithContent(new byte[] { 1, 2 });
var reader = new BufferReader(buffer);
Assert.Equal(1, reader.CurrentSegment[reader.CurrentSegmentIndex]);
Assert.Equal(1, reader.Read());
Assert.Equal(2, reader.CurrentSegment[reader.CurrentSegmentIndex]);
Assert.Equal(2, reader.Peek());
Assert.Equal(2, reader.Read());
Assert.Equal(-1, reader.Peek());
Assert.Equal(-1, reader.Read());
}
[Fact]
public void PeekWorksWithEmptySegments()
{
var buffer = Factory.CreateWithContent(new byte[] { 1 });
var reader = new BufferReader(buffer);
Assert.Equal(0, reader.CurrentSegmentIndex);
Assert.Equal(1, reader.CurrentSegment.Length);
Assert.Equal(1, reader.Peek());
Assert.Equal(1, reader.Read());
Assert.Equal(-1, reader.Peek());
Assert.Equal(-1, reader.Read());
}
[Fact]
public void WorksWithEmptyBuffer()
{
var reader = new BufferReader(Factory.CreateWithContent(new byte[] { }));
Assert.Equal(0, reader.CurrentSegmentIndex);
Assert.Equal(0, reader.CurrentSegment.Length);
Assert.Equal(-1, reader.Peek());
Assert.Equal(-1, reader.Read());
}
[Theory]
[InlineData(0, false)]
[InlineData(5, false)]
[InlineData(10, false)]
[InlineData(11, true)]
[InlineData(12, true)]
[InlineData(15, true)]
public void ReturnsCorrectCursor(int takes, bool end)
{
var readableBuffer = Factory.CreateWithContent(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var reader = new BufferReader(readableBuffer);
for (int i = 0; i < takes; i++)
{
reader.Read();
}
var expected = end ? new byte[] { } : readableBuffer.Slice((long)takes).ToArray();
Assert.Equal(expected, readableBuffer.Slice(reader.Position).ToArray());
}
[Fact]
public void SlicingBufferReturnsCorrectCursor()
{
var buffer = Factory.CreateWithContent(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var sliced = buffer.Slice(2L);
var reader = new BufferReader(sliced);
Assert.Equal(sliced.ToArray(), buffer.Slice(reader.Position).ToArray());
Assert.Equal(2, reader.Peek());
Assert.Equal(0, reader.CurrentSegmentIndex);
}
[Fact]
public void ReaderIndexIsCorrect()
{
var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
var reader = new BufferReader(buffer);
var counter = 1;
while (!reader.End)
{
var span = reader.CurrentSegment;
for (int i = reader.CurrentSegmentIndex; i < span.Length; i++)
{
Assert.Equal(counter++, reader.CurrentSegment[i]);
}
reader.Advance(span.Length);
}
Assert.Equal(buffer.Length, reader.ConsumedBytes);
}
}
}