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:
parent
d1aa53721b
commit
2c649b2409
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue