Remove usage of the Microsoft.Extensions.Buffers.Sources package, copy the source into Kestrel

This moves source code that used to be in aspnet/Common. It was only used here, so this simplifies the process of working with these internal-only APIs.

cref https://github.com/aspnet/Common/pull/386
This commit is contained in:
Nate McMaster 2018-07-11 22:31:23 -07:00
parent 181e521b40
commit 128eefdef3
No known key found for this signature in database
GPG Key ID: A778D9601BD78810
16 changed files with 1532 additions and 6 deletions

View File

@ -18,8 +18,6 @@
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.1</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>2.1.1</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
<MicrosoftExtensionsBuffersSourcesPackageVersion>2.1.1</MicrosoftExtensionsBuffersSourcesPackageVersion>
<MicrosoftExtensionsBuffersTestingSourcesPackageVersion>2.1.1</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.1</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.1.1</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.1</MicrosoftExtensionsConfigurationJsonPackageVersion>

View File

@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Buffers;
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;
public BufferReader(ReadOnlySequence<byte> buffer)
{
_end = false;
_index = 0;
_consumedBytes = 0;
_sequence = buffer;
_currentSequencePosition = _sequence.Start;
_nextSequencePosition = _currentSequencePosition;
_currentSpan = ReadOnlySpan<byte>.Empty;
MoveNext();
}
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;
}
public void Advance(int byteCount)
{
if (byteCount < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(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)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
}
}
}
}

View File

@ -0,0 +1,93 @@
// 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 System.Runtime.CompilerServices;
namespace System.Buffers
{
internal ref struct BufferWriter<T> where T: IBufferWriter<byte>
{
private T _output;
private Span<byte> _span;
private int _buffered;
public BufferWriter(T output)
{
_buffered = 0;
_output = output;
_span = output.GetSpan();
}
public Span<byte> Span => _span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Commit()
{
var buffered = _buffered;
if (buffered > 0)
{
_buffered = 0;
_output.Advance(buffered);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
_buffered += count;
_span = _span.Slice(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Ensure(int count = 1)
{
if (_span.Length < count)
{
EnsureMore(count);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
if (_buffered > 0)
{
Commit();
}
_output.GetMemory(count);
_span = _output.GetSpan();
}
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
}
}
}

View File

@ -11,6 +11,10 @@
<NoWarn>CS1591;$(NoWarn)</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\ThrowHelper.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
@ -21,7 +25,6 @@
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Buffers.Sources" Version="$(MicrosoftExtensionsBuffersSourcesPackageVersion)" PrivateAssets="All" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
<PackageReference Include="System.Numerics.Vectors" Version="$(SystemNumericsVectorsPackageVersion)" />

View File

@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#if DEBUG
using System.Threading;
using System.Diagnostics;
namespace System.Buffers
{
/// <summary>
/// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
/// individual blocks are then treated as independent array segments.
/// </summary>
internal sealed class MemoryPoolBlock : MemoryManager<byte>
{
private readonly int _offset;
private readonly int _length;
private int _pinCount;
/// <summary>
/// This object cannot be instantiated outside of the static Create method
/// </summary>
internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
{
_offset = offset;
_length = length;
Pool = pool;
Slab = slab;
}
/// <summary>
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
/// </summary>
public SlabMemoryPool Pool { get; }
/// <summary>
/// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
/// </summary>
public MemoryPoolSlab Slab { get; }
public override Memory<byte> Memory
{
get
{
if (!Slab.IsActive) ThrowHelper.ThrowObjectDisposedException(ExceptionArgument.MemoryPoolBlock);
return CreateMemory(_length);
}
}
#if BLOCK_LEASE_TRACKING
public bool IsLeased { get; set; }
public string Leaser { get; set; }
#endif
~MemoryPoolBlock()
{
if (Slab != null && Slab.IsActive)
{
Debug.Assert(false, $"{Environment.NewLine}{Environment.NewLine}*** Block being garbage collected instead of returned to pool" +
#if BLOCK_LEASE_TRACKING
$": {Leaser}" +
#endif
$" ***{ Environment.NewLine}");
// Need to make a new object because this one is being finalized
Pool.Return(new MemoryPoolBlock(Pool, Slab, _offset, _length));
}
}
protected override void Dispose(bool disposing)
{
if (!Slab.IsActive) ThrowHelper.ThrowObjectDisposedException(ExceptionArgument.MemoryPoolBlock);
if (Volatile.Read(ref _pinCount) > 0)
{
ThrowHelper.ThrowInvalidOperationException_ReturningPinnedBlock();
}
Pool.Return(this);
}
public override Span<byte> GetSpan() => new Span<byte>(Slab.Array, _offset, _length);
public override MemoryHandle Pin(int byteOffset = 0)
{
if (!Slab.IsActive) ThrowHelper.ThrowObjectDisposedException(ExceptionArgument.MemoryPoolBlock);
if (byteOffset < 0 || byteOffset > _length) ThrowHelper.ThrowArgumentOutOfRangeException(_length, byteOffset);
Interlocked.Increment(ref _pinCount);
unsafe
{
return new MemoryHandle((Slab.NativePointer + _offset + byteOffset).ToPointer(), default, this);
}
}
protected override bool TryGetArray(out ArraySegment<byte> segment)
{
segment = new ArraySegment<byte>(Slab.Array, _offset, _length);
return true;
}
public override void Unpin()
{
if (Interlocked.Decrement(ref _pinCount) < 0)
{
ThrowHelper.ThrowInvalidOperationException_ReferenceCountZero();
}
}
public void Lease()
{
#if BLOCK_LEASE_TRACKING
Leaser = Environment.StackTrace;
IsLeased = true;
#endif
}
}
}
#endif

View File

@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#if RELEASE
using System.Runtime.InteropServices;
namespace System.Buffers
{
/// <summary>
/// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
/// individual blocks are then treated as independent array segments.
/// </summary>
internal sealed class MemoryPoolBlock : IMemoryOwner<byte>
{
private readonly int _offset;
private readonly int _length;
/// <summary>
/// This object cannot be instantiated outside of the static Create method
/// </summary>
internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
{
_offset = offset;
_length = length;
Pool = pool;
Slab = slab;
Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length);
}
/// <summary>
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
/// </summary>
public SlabMemoryPool Pool { get; }
/// <summary>
/// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
/// </summary>
public MemoryPoolSlab Slab { get; }
public Memory<byte> Memory { get; }
~MemoryPoolBlock()
{
if (Slab != null && Slab.IsActive)
{
// Need to make a new object because this one is being finalized
Pool.Return(new MemoryPoolBlock(Pool, Slab, _offset, _length));
}
}
public void Dispose()
{
Pool.Return(this);
}
public void Lease()
{
}
}
}
#endif

View File

@ -0,0 +1,97 @@
// 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.InteropServices;
namespace System.Buffers
{
/// <summary>
/// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
/// individual blocks are then treated as independant array segments.
/// </summary>
internal class MemoryPoolSlab : IDisposable
{
/// <summary>
/// This handle pins the managed array in memory until the slab is disposed. This prevents it from being
/// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls.
/// </summary>
private readonly GCHandle _gcHandle;
private readonly IntPtr _nativePointer;
private byte[] _data;
private bool _isActive;
private bool _disposedValue;
public MemoryPoolSlab(byte[] data)
{
_data = data;
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
_nativePointer = _gcHandle.AddrOfPinnedObject();
_isActive = true;
}
/// <summary>
/// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the
/// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the
/// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will
/// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage
/// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will
/// be unpinned by the slab's Dispose.
/// </summary>
public bool IsActive => _isActive;
public IntPtr NativePointer => _nativePointer;
public byte[] Array => _data;
public int Length => _data.Length;
public static MemoryPoolSlab Create(int length)
{
// allocate and pin requested memory length
var array = new byte[length];
// allocate and return slab tracking object
return new MemoryPoolSlab(array);
}
protected void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// N/A: dispose managed state (managed objects).
}
_isActive = false;
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();
}
// set large fields to null.
_data = null;
_disposedValue = true;
}
}
// override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
~MemoryPoolSlab()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// uncomment the following line if the finalizer is overridden above.
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,193 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Diagnostics;
namespace System.Buffers
{
/// <summary>
/// Used to allocate and distribute re-usable blocks of memory.
/// </summary>
internal class SlabMemoryPool : MemoryPool<byte>
{
/// <summary>
/// The size of a block. 4096 is chosen because most operating systems use 4k pages.
/// </summary>
private const int _blockSize = 4096;
/// <summary>
/// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory
/// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively
/// affect memory management's compactification.
/// </summary>
private const int _blockCount = 32;
/// <summary>
/// Max allocation block size for pooled blocks,
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
/// </summary>
public override int MaxBufferSize { get; } = _blockSize;
/// <summary>
/// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
/// </summary>
private static readonly int _slabLength = _blockSize * _blockCount;
/// <summary>
/// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects
/// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added.
/// </summary>
private readonly ConcurrentQueue<MemoryPoolBlock> _blocks = new ConcurrentQueue<MemoryPoolBlock>();
/// <summary>
/// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive,
/// the blocks will be added to _blocks when returned.
/// </summary>
private readonly ConcurrentStack<MemoryPoolSlab> _slabs = new ConcurrentStack<MemoryPoolSlab>();
/// <summary>
/// This is part of implementing the IDisposable pattern.
/// </summary>
private bool _disposedValue = false; // To detect redundant calls
/// <summary>
/// This default value passed in to Rent to use the default value for the pool.
/// </summary>
private const int AnySize = -1;
public override IMemoryOwner<byte> Rent(int size = AnySize)
{
if (size == AnySize) size = _blockSize;
else if (size > _blockSize)
{
ThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize);
}
var block = Lease();
return block;
}
/// <summary>
/// Called to take a block from the pool.
/// </summary>
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
private MemoryPoolBlock Lease()
{
Debug.Assert(!_disposedValue, "Block being leased from disposed pool!");
if (_blocks.TryDequeue(out MemoryPoolBlock block))
{
// block successfully taken from the stack - return it
block.Lease();
return block;
}
// no blocks available - grow the pool
block = AllocateSlab();
block.Lease();
return block;
}
/// <summary>
/// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
/// block tracking objects, and adds them all to the pool.
/// </summary>
private MemoryPoolBlock AllocateSlab()
{
var slab = MemoryPoolSlab.Create(_slabLength);
_slabs.Push(slab);
var basePtr = slab.NativePointer;
// Page align the blocks
var firstOffset = (int)((((ulong)basePtr + (uint)_blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr);
// Ensure page aligned
Debug.Assert((((ulong)basePtr + (uint)firstOffset) & (uint)(_blockSize - 1)) == 0);
var blockAllocationLength = ((_slabLength - firstOffset) & ~(_blockSize - 1));
var offset = firstOffset;
for (;
offset + _blockSize < blockAllocationLength;
offset += _blockSize)
{
var block = new MemoryPoolBlock(
this,
slab,
offset,
_blockSize);
#if BLOCK_LEASE_TRACKING
block.IsLeased = true;
#endif
Return(block);
}
Debug.Assert(offset + _blockSize - firstOffset == blockAllocationLength);
// return last block rather than adding to pool
var newBlock = new MemoryPoolBlock(
this,
slab,
offset,
_blockSize);
return newBlock;
}
/// <summary>
/// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and
/// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the
/// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and return
/// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to avoid
/// leaving "dead zones" in the slab due to lost block tracking objects.
/// </summary>
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
internal void Return(MemoryPoolBlock block)
{
#if BLOCK_LEASE_TRACKING
Debug.Assert(block.Pool == this, "Returned block was not leased from this pool");
Debug.Assert(block.IsLeased, $"Block being returned to pool twice: {block.Leaser}{Environment.NewLine}");
block.IsLeased = false;
#endif
if (block.Slab != null && block.Slab.IsActive)
{
_blocks.Enqueue(block);
}
else
{
GC.SuppressFinalize(block);
}
}
protected override void Dispose(bool disposing)
{
if (!_disposedValue)
{
_disposedValue = true;
#if DEBUG && !INNER_LOOP
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
#endif
if (disposing)
{
while (_slabs.TryPop(out MemoryPoolSlab slab))
{
// dispose managed state (managed objects).
slab.Dispose();
}
}
// Discard blocks in pool
while (_blocks.TryDequeue(out MemoryPoolBlock block))
{
GC.SuppressFinalize(block);
}
// N/A: free unmanaged resources (unmanaged objects) and override a finalizer below.
// N/A: set large fields to null.
}
}
}
}

View File

@ -13,10 +13,11 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Connections.Abstractions\Connections.Abstractions.csproj" />
<Compile Include="..\shared\ThrowHelper.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Buffers.Sources" Version="$(MicrosoftExtensionsBuffersSourcesPackageVersion)" PrivateAssets="All" />
<ProjectReference Include="..\Connections.Abstractions\Connections.Abstractions.csproj" />
</ItemGroup>
</Project>

87
src/shared/ThrowHelper.cs Normal file
View File

@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace System.Buffers
{
internal class ThrowHelper
{
public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset)
{
throw GetArgumentOutOfRangeException(sourceLength, offset);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset)
{
if ((uint)offset > (uint)sourceLength)
{
// Offset is negative or less than array length
return new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset));
}
// The third parameter (not passed) length must be out of range
return new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length));
}
public static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
{
throw GetArgumentOutOfRangeException(argument);
}
public static void ThrowInvalidOperationException_ReferenceCountZero()
{
throw new InvalidOperationException("Can't release when reference count is already zero");
}
public static void ThrowInvalidOperationException_ReturningPinnedBlock()
{
throw new InvalidOperationException("Can't release when reference count is already zero");
}
public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
{
throw GetArgumentOutOfRangeException_BufferRequestTooLarge(maxSize);
}
public static void ThrowObjectDisposedException(ExceptionArgument argument)
{
throw GetObjectDisposedException(argument);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument)
{
return new ArgumentOutOfRangeException(GetArgumentName(argument));
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
{
return new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.size), $"Cannot allocate more than {maxSize} bytes in a single buffer");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ObjectDisposedException GetObjectDisposedException(ExceptionArgument argument)
{
return new ObjectDisposedException(GetArgumentName(argument));
}
private static string GetArgumentName(ExceptionArgument argument)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument), "The enum value is not defined, please check the ExceptionArgument Enum.");
return argument.ToString();
}
}
internal enum ExceptionArgument
{
size,
offset,
length,
MemoryPoolBlock
}
}

View File

@ -0,0 +1,300 @@
// 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 PeekReturnsMinuOneByteInTheEnd()
{
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 PeekWorkesWithEmptySegments()
{
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 WorkesWithEmptyBuffer()
{
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);
}
}
}

View File

@ -0,0 +1,223 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace System.IO.Pipelines.Tests
{
public class BufferWriterTests : IDisposable
{
protected Pipe Pipe;
public BufferWriterTests()
{
Pipe = new Pipe(new PipeOptions(useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0));
}
public void Dispose()
{
Pipe.Writer.Complete();
Pipe.Reader.Complete();
}
private byte[] Read()
{
Pipe.Writer.FlushAsync().GetAwaiter().GetResult();
Pipe.Writer.Complete();
ReadResult readResult = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
byte[] data = readResult.Buffer.ToArray();
Pipe.Reader.AdvanceTo(readResult.Buffer.End);
return data;
}
[Theory]
[InlineData(3, -1, 0)]
[InlineData(3, 0, -1)]
[InlineData(3, 0, 4)]
[InlineData(3, 4, 0)]
[InlineData(3, -1, -1)]
[InlineData(3, 4, 4)]
public void ThrowsForInvalidParameters(int arrayLength, int offset, int length)
{
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
var array = new byte[arrayLength];
for (var i = 0; i < array.Length; i++)
{
array[i] = (byte)(i + 1);
}
writer.Write(new Span<byte>(array, 0, 0));
writer.Write(new Span<byte>(array, array.Length, 0));
try
{
writer.Write(new Span<byte>(array, offset, length));
Assert.True(false);
}
catch (Exception ex)
{
Assert.True(ex is ArgumentOutOfRangeException);
}
writer.Write(new Span<byte>(array, 0, array.Length));
writer.Commit();
Assert.Equal(array, Read());
}
[Theory]
[InlineData(0, 3)]
[InlineData(1, 2)]
[InlineData(2, 1)]
[InlineData(1, 1)]
public void CanWriteWithOffsetAndLenght(int offset, int length)
{
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
var array = new byte[] { 1, 2, 3 };
writer.Write(new Span<byte>(array, offset, length));
writer.Commit();
Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read());
}
[Fact]
public void CanWriteEmpty()
{
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
var array = new byte[] { };
writer.Write(array);
writer.Write(new Span<byte>(array, 0, array.Length));
writer.Commit();
Assert.Equal(array, Read());
}
[Fact]
public void CanWriteIntoHeadlessBuffer()
{
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
writer.Write(new byte[] { 1, 2, 3 });
writer.Commit();
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
}
[Fact]
public void CanWriteMultipleTimes()
{
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
writer.Write(new byte[] { 1 });
writer.Write(new byte[] { 2 });
writer.Write(new byte[] { 3 });
writer.Commit();
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
}
[Fact]
public void CanWriteOverTheBlockLength()
{
Memory<byte> memory = Pipe.Writer.GetMemory();
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
IEnumerable<byte> source = Enumerable.Range(0, memory.Length).Select(i => (byte)i);
byte[] expectedBytes = source.Concat(source).Concat(source).ToArray();
writer.Write(expectedBytes);
writer.Commit();
Assert.Equal(expectedBytes, Read());
}
[Fact]
public void EnsureAllocatesSpan()
{
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
writer.Ensure(10);
Assert.True(writer.Span.Length > 10);
Assert.Equal(new byte[] { }, Read());
}
[Fact]
public void ExposesSpan()
{
int initialLength = Pipe.Writer.GetMemory().Length;
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
Assert.Equal(initialLength, writer.Span.Length);
Assert.Equal(new byte[] { }, Read());
}
[Fact]
public void SlicesSpanAndAdvancesAfterWrite()
{
int initialLength = Pipe.Writer.GetMemory().Length;
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
writer.Write(new byte[] { 1, 2, 3 });
writer.Commit();
Assert.Equal(initialLength - 3, writer.Span.Length);
Assert.Equal(Pipe.Writer.GetMemory().Length, writer.Span.Length);
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
}
[Theory]
[InlineData(5)]
[InlineData(50)]
[InlineData(500)]
[InlineData(5000)]
[InlineData(50000)]
public async Task WriteLargeDataBinary(int length)
{
var data = new byte[length];
new Random(length).NextBytes(data);
PipeWriter output = Pipe.Writer;
output.Write(data);
await output.FlushAsync();
ReadResult result = await Pipe.Reader.ReadAsync();
ReadOnlySequence<byte> input = result.Buffer;
Assert.Equal(data, input.ToArray());
Pipe.Reader.AdvanceTo(input.End);
}
[Fact]
public async Task CanWriteNothingToBuffer()
{
PipeWriter buffer = Pipe.Writer;
buffer.GetMemory(0);
buffer.Advance(0); // doing nothing, the hard way
await buffer.FlushAsync();
}
[Fact]
public void EmptyWriteDoesNotThrow()
{
Pipe.Writer.Write(new byte[0]);
}
[Fact]
public void ThrowsOnAdvanceOverMemorySize()
{
Memory<byte> buffer = Pipe.Writer.GetMemory(1);
var exception = Assert.Throws<InvalidOperationException>(() => Pipe.Writer.Advance(buffer.Length + 1));
Assert.Equal("Can't advance past buffer size.", exception.Message);
}
[Fact]
public void ThrowsOnAdvanceWithNoMemory()
{
PipeWriter buffer = Pipe.Writer;
var exception = Assert.Throws<InvalidOperationException>(() => buffer.Advance(1));
Assert.Equal("No writing operation. Make sure GetMemory() was called.", exception.Message);
}
}
}

View File

@ -23,7 +23,6 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Buffers.Testing.Sources" Version="$(MicrosoftExtensionsBuffersTestingSourcesPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
// 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.
namespace System.Buffers
{
internal class BufferSegment : ReadOnlySequenceSegment<byte>
{
public BufferSegment(Memory<byte> memory)
{
Memory = memory;
}
public BufferSegment Append(Memory<byte> memory)
{
var segment = new BufferSegment(memory)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = segment;
return segment;
}
}
}

View File

@ -0,0 +1,45 @@
// 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.
namespace System.Buffers
{
internal class CustomMemoryForTest<T> : IMemoryOwner<T>
{
private bool _disposed;
private T[] _array;
private readonly int _offset;
private readonly int _length;
public CustomMemoryForTest(T[] array): this(array, 0, array.Length)
{
}
public CustomMemoryForTest(T[] array, int offset, int length)
{
_array = array;
_offset = offset;
_length = length;
}
public Memory<T> Memory
{
get
{
if (_disposed)
throw new ObjectDisposedException(nameof(CustomMemoryForTest<T>));
return new Memory<T>(_array, _offset, _length);
}
}
public void Dispose()
{
if (_disposed)
return;
_array = null;
_disposed = true;
}
}
}

View File

@ -0,0 +1,148 @@
// 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 System.Collections.Generic;
using System.Text;
namespace System.Buffers
{
internal abstract class ReadOnlySequenceFactory
{
public static ReadOnlySequenceFactory ArrayFactory { get; } = new ArrayTestSequenceFactory();
public static ReadOnlySequenceFactory MemoryFactory { get; } = new MemoryTestSequenceFactory();
public static ReadOnlySequenceFactory OwnedMemoryFactory { get; } = new OwnedMemoryTestSequenceFactory();
public static ReadOnlySequenceFactory SingleSegmentFactory { get; } = new SingleSegmentTestSequenceFactory();
public static ReadOnlySequenceFactory SegmentPerByteFactory { get; } = new BytePerSegmentTestSequenceFactory();
public abstract ReadOnlySequence<byte> CreateOfSize(int size);
public abstract ReadOnlySequence<byte> CreateWithContent(byte[] data);
public ReadOnlySequence<byte> CreateWithContent(string data)
{
return CreateWithContent(Encoding.ASCII.GetBytes(data));
}
internal class ArrayTestSequenceFactory : ReadOnlySequenceFactory
{
public override ReadOnlySequence<byte> CreateOfSize(int size)
{
return new ReadOnlySequence<byte>(new byte[size + 20], 10, size);
}
public override ReadOnlySequence<byte> CreateWithContent(byte[] data)
{
var startSegment = new byte[data.Length + 20];
Array.Copy(data, 0, startSegment, 10, data.Length);
return new ReadOnlySequence<byte>(startSegment, 10, data.Length);
}
}
internal class MemoryTestSequenceFactory : ReadOnlySequenceFactory
{
public override ReadOnlySequence<byte> CreateOfSize(int size)
{
return CreateWithContent(new byte[size]);
}
public override ReadOnlySequence<byte> CreateWithContent(byte[] data)
{
var startSegment = new byte[data.Length + 20];
Array.Copy(data, 0, startSegment, 10, data.Length);
return new ReadOnlySequence<byte>(new Memory<byte>(startSegment, 10, data.Length));
}
}
internal class OwnedMemoryTestSequenceFactory : ReadOnlySequenceFactory
{
public override ReadOnlySequence<byte> CreateOfSize(int size)
{
return CreateWithContent(new byte[size]);
}
public override ReadOnlySequence<byte> CreateWithContent(byte[] data)
{
var startSegment = new byte[data.Length + 20];
Array.Copy(data, 0, startSegment, 10, data.Length);
return new ReadOnlySequence<byte>(new CustomMemoryForTest<byte>(startSegment, 10, data.Length).Memory);
}
}
internal class SingleSegmentTestSequenceFactory : ReadOnlySequenceFactory
{
public override ReadOnlySequence<byte> CreateOfSize(int size)
{
return CreateWithContent(new byte[size]);
}
public override ReadOnlySequence<byte> CreateWithContent(byte[] data)
{
return CreateSegments(data);
}
}
internal class BytePerSegmentTestSequenceFactory : ReadOnlySequenceFactory
{
public override ReadOnlySequence<byte> CreateOfSize(int size)
{
return CreateWithContent(new byte[size]);
}
public override ReadOnlySequence<byte> CreateWithContent(byte[] data)
{
var segments = new List<byte[]>();
segments.Add(Array.Empty<byte>());
foreach (var b in data)
{
segments.Add(new[] { b });
segments.Add(Array.Empty<byte>());
}
return CreateSegments(segments.ToArray());
}
}
public static ReadOnlySequence<byte> CreateSegments(params byte[][] inputs)
{
if (inputs == null || inputs.Length == 0)
{
throw new InvalidOperationException();
}
int i = 0;
BufferSegment last = null;
BufferSegment first = null;
do
{
byte[] s = inputs[i];
int length = s.Length;
int dataOffset = length;
var chars = new byte[length * 2];
for (int j = 0; j < length; j++)
{
chars[dataOffset + j] = s[j];
}
// Create a segment that has offset relative to the OwnedMemory and OwnedMemory itself has offset relative to array
var memory = new Memory<byte>(chars).Slice(length, length);
if (first == null)
{
first = new BufferSegment(memory);
last = first;
}
else
{
last = last.Append(memory);
}
i++;
} while (i < inputs.Length);
return new ReadOnlySequence<byte>(first, 0, last, last.Memory.Length);
}
}
}