Merge branch 'release/2.2'
This commit is contained in:
commit
a0f30fc2a5
|
|
@ -1,7 +1,14 @@
|
||||||
{
|
{
|
||||||
"Default": {
|
"nonshipping": {
|
||||||
"rules": [
|
"rules": [],
|
||||||
"DefaultCompositeRule"
|
"packages": {
|
||||||
]
|
"Microsoft.Extensions.Buffers.MemoryPool.Sources": {},
|
||||||
|
"Microsoft.Extensions.Buffers.Testing.Sources": {}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Default": {
|
||||||
|
"rules": [
|
||||||
|
"DefaultCompositeRule"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,9 +19,6 @@
|
||||||
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-alpha1-10123</MicrosoftAspNetCoreTestingPackageVersion>
|
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-alpha1-10123</MicrosoftAspNetCoreTestingPackageVersion>
|
||||||
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>3.0.0-alpha1-10123</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>3.0.0-alpha1-10123</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
||||||
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
|
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
|
||||||
<MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion>
|
|
||||||
<MicrosoftExtensionsBuffersSourcesPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsBuffersSourcesPackageVersion>
|
|
||||||
<MicrosoftExtensionsBuffersTestingSourcesPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
|
|
||||||
<MicrosoftExtensionsConfigurationBinderPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsConfigurationBinderPackageVersion>
|
<MicrosoftExtensionsConfigurationBinderPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsConfigurationBinderPackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
|
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationJsonPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsConfigurationJsonPackageVersion>
|
<MicrosoftExtensionsConfigurationJsonPackageVersion>3.0.0-alpha1-10123</MicrosoftExtensionsConfigurationJsonPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace System.Buffers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to allocate and distribute re-usable blocks of memory.
|
||||||
|
/// </summary>
|
||||||
|
internal class DiagnosticMemoryPool : MemoryPool<byte>
|
||||||
|
{
|
||||||
|
private readonly MemoryPool<byte> _pool;
|
||||||
|
|
||||||
|
private readonly bool _allowLateReturn;
|
||||||
|
|
||||||
|
private readonly bool _rentTracking;
|
||||||
|
|
||||||
|
private readonly object _syncObj;
|
||||||
|
|
||||||
|
private readonly HashSet<DiagnosticPoolBlock> _blocks;
|
||||||
|
|
||||||
|
private readonly List<Exception> _blockAccessExceptions;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<object> _allBlocksRetuned;
|
||||||
|
|
||||||
|
private int _totalBlocks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This default value passed in to Rent to use the default value for the pool.
|
||||||
|
/// </summary>
|
||||||
|
private const int AnySize = -1;
|
||||||
|
|
||||||
|
public DiagnosticMemoryPool(MemoryPool<byte> pool, bool allowLateReturn = false, bool rentTracking = false)
|
||||||
|
{
|
||||||
|
_pool = pool;
|
||||||
|
_allowLateReturn = allowLateReturn;
|
||||||
|
_rentTracking = rentTracking;
|
||||||
|
_blocks = new HashSet<DiagnosticPoolBlock>();
|
||||||
|
_syncObj = new object();
|
||||||
|
_allBlocksRetuned = new TaskCompletionSource<object>();
|
||||||
|
_blockAccessExceptions = new List<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
public override IMemoryOwner<byte> Rent(int size = AnySize)
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
var diagnosticPoolBlock = new DiagnosticPoolBlock(this, _pool.Rent(size));
|
||||||
|
if (_rentTracking)
|
||||||
|
{
|
||||||
|
diagnosticPoolBlock.Track();
|
||||||
|
}
|
||||||
|
_totalBlocks++;
|
||||||
|
_blocks.Add(diagnosticPoolBlock);
|
||||||
|
return diagnosticPoolBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int MaxBufferSize => _pool.MaxBufferSize;
|
||||||
|
|
||||||
|
internal void Return(DiagnosticPoolBlock block)
|
||||||
|
{
|
||||||
|
bool returnedAllBlocks;
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
_blocks.Remove(block);
|
||||||
|
returnedAllBlocks = _blocks.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
if (!_allowLateReturn)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockReturnedToDisposedPool(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnedAllBlocks)
|
||||||
|
{
|
||||||
|
SetAllBlocksReturned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ReportException(Exception exception)
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
_blockAccessExceptions.Add(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_DoubleDispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allBlocksReturned = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
IsDisposed = true;
|
||||||
|
allBlocksReturned = _blocks.Count == 0;
|
||||||
|
if (!allBlocksReturned && !_allowLateReturn)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_blockAccessExceptions.Any())
|
||||||
|
{
|
||||||
|
throw CreateAccessExceptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (allBlocksReturned)
|
||||||
|
{
|
||||||
|
SetAllBlocksReturned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAllBlocksReturned()
|
||||||
|
{
|
||||||
|
if (_blockAccessExceptions.Any())
|
||||||
|
{
|
||||||
|
_allBlocksRetuned.SetException(CreateAccessExceptions());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_allBlocksRetuned.SetResult(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AggregateException CreateAccessExceptions()
|
||||||
|
{
|
||||||
|
return new AggregateException("Exceptions occurred while accessing blocks", _blockAccessExceptions.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WhenAllBlocksReturnedAsync(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var task = await Task.WhenAny(_allBlocksRetuned.Task, Task.Delay(timeout));
|
||||||
|
if (task != _allBlocksRetuned.Task)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlocksWereNotReturnedInTime(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
await task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using System.Diagnostics;
|
||||||
|
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 DiagnosticPoolBlock : MemoryManager<byte>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
|
||||||
|
/// </summary>
|
||||||
|
private readonly DiagnosticMemoryPool _pool;
|
||||||
|
|
||||||
|
private readonly IMemoryOwner<byte> _memoryOwner;
|
||||||
|
private MemoryHandle? _memoryHandle;
|
||||||
|
private Memory<byte> _memory;
|
||||||
|
|
||||||
|
private readonly object _syncObj = new object();
|
||||||
|
private bool _isDisposed;
|
||||||
|
private int _pinCount;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This object cannot be instantiated outside of the static Create method
|
||||||
|
/// </summary>
|
||||||
|
internal DiagnosticPoolBlock(DiagnosticMemoryPool pool, IMemoryOwner<byte> memoryOwner)
|
||||||
|
{
|
||||||
|
_pool = pool;
|
||||||
|
_memoryOwner = memoryOwner;
|
||||||
|
_memory = memoryOwner.Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Memory<byte> Memory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pool.IsDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateMemory(_memory.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_pool.ReportException(exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (Volatile.Read(ref _pinCount) > 0)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_ReturningPinnedBlock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockDoubleDispose(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_memoryOwner.Dispose();
|
||||||
|
|
||||||
|
_pool.Return(this);
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_pool.ReportException(exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Span<byte> GetSpan()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pool.IsDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _memory.Span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_pool.ReportException(exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MemoryHandle Pin(int byteOffset = 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pool.IsDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteOffset < 0 || byteOffset > _memory.Length)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException(_memory.Length, byteOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pinCount++;
|
||||||
|
|
||||||
|
_memoryHandle = _memoryHandle ?? _memory.Pin();
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
return new MemoryHandle(((IntPtr)_memoryHandle.Value.Pointer + byteOffset).ToPointer(), default, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_pool.ReportException(exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TryGetArray(out ArraySegment<byte> segment)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pool.IsDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MemoryMarshal.TryGetArray(_memory, out segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_pool.ReportException(exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Unpin()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncObj)
|
||||||
|
{
|
||||||
|
if (_pinCount == 0)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowInvalidOperationException_PinCountZero(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pinCount--;
|
||||||
|
|
||||||
|
if (_pinCount == 0)
|
||||||
|
{
|
||||||
|
Debug.Assert(_memoryHandle.HasValue);
|
||||||
|
_memoryHandle.Value.Dispose();
|
||||||
|
_memoryHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_pool.ReportException(exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackTrace Leaser { get; set; }
|
||||||
|
|
||||||
|
public void Track()
|
||||||
|
{
|
||||||
|
Leaser = new StackTrace(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// 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>
|
||||||
|
/// 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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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 GCHandle _gcHandle;
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
public MemoryPoolSlab(byte[] data)
|
||||||
|
{
|
||||||
|
Array = data;
|
||||||
|
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||||
|
NativePointer = _gcHandle.AddrOfPinnedObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 => !_isDisposed;
|
||||||
|
|
||||||
|
public IntPtr NativePointer { get; private set; }
|
||||||
|
|
||||||
|
public byte[] Array { get; private set; }
|
||||||
|
|
||||||
|
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 (_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
|
||||||
|
Array = null;
|
||||||
|
NativePointer = IntPtr.Zero;;
|
||||||
|
|
||||||
|
if (_gcHandle.IsAllocated)
|
||||||
|
{
|
||||||
|
_gcHandle.Free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~MemoryPoolSlab()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace System.Buffers
|
||||||
|
{
|
||||||
|
internal class MemoryPoolThrowHelper
|
||||||
|
{
|
||||||
|
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 ThrowInvalidOperationException_PinCountZero(DiagnosticPoolBlock block)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(GenerateMessage("Can't unpin, pin count is zero", block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_ReturningPinnedBlock(DiagnosticPoolBlock block)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(GenerateMessage("Disposing pinned block", block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_DoubleDispose()
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Object is being disposed twice");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_BlockDoubleDispose(DiagnosticPoolBlock block)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Block is being disposed twice");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_BlockReturnedToDisposedPool(DiagnosticPoolBlock block)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(GenerateMessage("Block is being returned to disposed pool", block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(DiagnosticPoolBlock block)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(GenerateMessage("Block is backed by disposed slab", block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(int returned, int total, DiagnosticPoolBlock[] blocks)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(GenerateMessage($"Memory pool with active blocks is being disposed, {returned} of {total} returned", blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowInvalidOperationException_BlocksWereNotReturnedInTime(int returned, int total, DiagnosticPoolBlock[] blocks)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(GenerateMessage($"Blocks were not returned in time, {returned} of {total} returned ", blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateMessage(string message, params DiagnosticPoolBlock[] blocks)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder(message);
|
||||||
|
foreach (var diagnosticPoolBlock in blocks)
|
||||||
|
{
|
||||||
|
if (diagnosticPoolBlock.Leaser != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
builder.AppendLine("Block leased from:");
|
||||||
|
builder.AppendLine(diagnosticPoolBlock.Leaser.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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,
|
||||||
|
MemoryPool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
// 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;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
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 _isDisposed; // To detect redundant calls
|
||||||
|
|
||||||
|
private int _totalAllocatedBlocks;
|
||||||
|
|
||||||
|
private readonly object _disposeSync = new object();
|
||||||
|
|
||||||
|
/// <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 > _blockSize)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.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()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 offset = (int)((((ulong)basePtr + (uint)_blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr);
|
||||||
|
// Ensure page aligned
|
||||||
|
Debug.Assert(((ulong)basePtr + (uint)offset) % _blockSize == 0);
|
||||||
|
|
||||||
|
var blockCount = (_slabLength - offset) / _blockSize;
|
||||||
|
Interlocked.Add(ref _totalAllocatedBlocks, blockCount);
|
||||||
|
|
||||||
|
MemoryPoolBlock block = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < blockCount; i++)
|
||||||
|
{
|
||||||
|
block = new MemoryPoolBlock(this, slab, offset, _blockSize);
|
||||||
|
|
||||||
|
if (i != blockCount - 1) // last block
|
||||||
|
{
|
||||||
|
#if BLOCK_LEASE_TRACKING
|
||||||
|
block.IsLeased = true;
|
||||||
|
#endif
|
||||||
|
Return(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += _blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 (!_isDisposed)
|
||||||
|
{
|
||||||
|
_blocks.Enqueue(block);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_disposeSync)
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
|
@ -6,16 +5,14 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace System.Buffers
|
namespace System.Buffers
|
||||||
{
|
{
|
||||||
// TODO: Once this is public, update the actual CountingBufferWriter in the Common repo,
|
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
|
||||||
// and go back to using that.
|
|
||||||
internal ref struct CountingBufferWriter<T> where T: IBufferWriter<byte>
|
|
||||||
{
|
{
|
||||||
private T _output;
|
private T _output;
|
||||||
private Span<byte> _span;
|
private Span<byte> _span;
|
||||||
private int _buffered;
|
private int _buffered;
|
||||||
private long _bytesCommitted;
|
private long _bytesCommitted;
|
||||||
|
|
||||||
public CountingBufferWriter(T output)
|
public BufferWriter(T output)
|
||||||
{
|
{
|
||||||
_buffered = 0;
|
_buffered = 0;
|
||||||
_bytesCommitted = 0;
|
_bytesCommitted = 0;
|
||||||
|
|
@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return new ArraySegment<byte>(bytes, offset, 10 - offset);
|
return new ArraySegment<byte>(bytes, offset, 10 - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static int WriteBeginChunkBytes(ref CountingBufferWriter<PipeWriter> start, int dataCount)
|
internal static int WriteBeginChunkBytes(ref BufferWriter<PipeWriter> start, int dataCount)
|
||||||
{
|
{
|
||||||
var chunkSegment = BeginChunkBytes(dataCount);
|
var chunkSegment = BeginChunkBytes(dataCount);
|
||||||
start.Write(new ReadOnlySpan<byte>(chunkSegment.Array, chunkSegment.Offset, chunkSegment.Count));
|
start.Write(new ReadOnlySpan<byte>(chunkSegment.Array, chunkSegment.Offset, chunkSegment.Count));
|
||||||
return chunkSegment.Count;
|
return chunkSegment.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void WriteEndChunkBytes(ref CountingBufferWriter<PipeWriter> start)
|
internal static void WriteEndChunkBytes(ref BufferWriter<PipeWriter> start)
|
||||||
{
|
{
|
||||||
start.Write(new ReadOnlySpan<byte>(_endChunkBytes.Array, _endChunkBytes.Offset, _endChunkBytes.Count));
|
start.Write(new ReadOnlySpan<byte>(_endChunkBytes.Array, _endChunkBytes.Offset, _endChunkBytes.Count));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = _pipeWriter;
|
var buffer = _pipeWriter;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(buffer);
|
var writer = new BufferWriter<PipeWriter>(buffer);
|
||||||
|
|
||||||
writer.Write(_bytesHttpVersion11);
|
writer.Write(_bytesHttpVersion11);
|
||||||
var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase);
|
var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase);
|
||||||
|
|
@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(_pipeWriter);
|
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
||||||
if (buffer.Length > 0)
|
if (buffer.Length > 0)
|
||||||
{
|
{
|
||||||
writer.Write(buffer);
|
writer.Write(buffer);
|
||||||
|
|
|
||||||
|
|
@ -7765,7 +7765,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CopyToFast(ref CountingBufferWriter<PipeWriter> output)
|
internal void CopyToFast(ref BufferWriter<PipeWriter> output)
|
||||||
{
|
{
|
||||||
var tempBits = _bits | (_contentLength.HasValue ? -9223372036854775808L : 0);
|
var tempBits = _bits | (_contentLength.HasValue ? -9223372036854775808L : 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -936,7 +936,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
var bytesWritten = 0L;
|
var bytesWritten = 0L;
|
||||||
if (buffer.Length > 0)
|
if (buffer.Length > 0)
|
||||||
{
|
{
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(writableBuffer);
|
var writer = new BufferWriter<PipeWriter>(writableBuffer);
|
||||||
|
|
||||||
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length);
|
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length);
|
||||||
writer.Write(buffer.Span);
|
writer.Write(buffer.Span);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CopyTo(ref CountingBufferWriter<PipeWriter> buffer)
|
internal void CopyTo(ref BufferWriter<PipeWriter> buffer)
|
||||||
{
|
{
|
||||||
CopyToFast(ref buffer);
|
CopyToFast(ref buffer);
|
||||||
if (MaybeUnknown != null)
|
if (MaybeUnknown != null)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static unsafe void WriteAsciiNoValidation(ref this CountingBufferWriter<PipeWriter> buffer, string data)
|
internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter<PipeWriter> buffer, string data)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(data))
|
if (string.IsNullOrEmpty(data))
|
||||||
{
|
{
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static unsafe void WriteNumeric(ref this CountingBufferWriter<PipeWriter> buffer, ulong number)
|
internal static unsafe void WriteNumeric(ref this BufferWriter<PipeWriter> buffer, ulong number)
|
||||||
{
|
{
|
||||||
const byte AsciiDigitStart = (byte)'0';
|
const byte AsciiDigitStart = (byte)'0';
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static void WriteNumericMultiWrite(ref this CountingBufferWriter<PipeWriter> buffer, ulong number)
|
private static void WriteNumericMultiWrite(ref this BufferWriter<PipeWriter> buffer, ulong number)
|
||||||
{
|
{
|
||||||
const byte AsciiDigitStart = (byte)'0';
|
const byte AsciiDigitStart = (byte)'0';
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private unsafe static void WriteAsciiMultiWrite(ref this CountingBufferWriter<PipeWriter> buffer, string data)
|
private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data)
|
||||||
{
|
{
|
||||||
var remaining = data.Length;
|
var remaining = data.Length;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@
|
||||||
<NoWarn>CS1591;$(NoWarn)</NoWarn>
|
<NoWarn>CS1591;$(NoWarn)</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\shared\ThrowHelper.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
|
||||||
|
|
@ -21,8 +25,6 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
|
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
|
||||||
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
|
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Buffers.Sources" Version="$(MicrosoftExtensionsBuffersSourcesPackageVersion)" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Buffers.MemoryPool.Sources" Version="$(MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion)" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
||||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="$(SystemNumericsVectorsPackageVersion)" />
|
<PackageReference Include="System.Numerics.Vectors" Version="$(SystemNumericsVectorsPackageVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,12 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Connections.Abstractions\Connections.Abstractions.csproj" />
|
<Compile Include="..\shared\ThrowHelper.cs" />
|
||||||
|
<Compile Include="..\..\shared\Microsoft.Extensions.Buffers.MemoryPool.Sources\*.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Buffers.Sources" Version="$(MicrosoftExtensionsBuffersSourcesPackageVersion)" PrivateAssets="All" />
|
<ProjectReference Include="..\Connections.Abstractions\Connections.Abstractions.csproj" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Buffers.MemoryPool.Sources" Version="$(MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion)" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// 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.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// 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 BuffersThrowHelper
|
||||||
|
{
|
||||||
|
public static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
|
||||||
|
{
|
||||||
|
throw GetArgumentOutOfRangeException(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument)
|
||||||
|
{
|
||||||
|
return new ArgumentOutOfRangeException(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
|
||||||
|
{
|
||||||
|
length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
// 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 CanWriteWithOffsetAndLength(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));
|
||||||
|
|
||||||
|
Assert.Equal(0, writer.BytesCommitted);
|
||||||
|
|
||||||
|
writer.Commit();
|
||||||
|
|
||||||
|
Assert.Equal(length, writer.BytesCommitted);
|
||||||
|
Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read());
|
||||||
|
Assert.Equal(length, writer.BytesCommitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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(0, writer.BytesCommitted);
|
||||||
|
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(3, writer.BytesCommitted);
|
||||||
|
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(3, writer.BytesCommitted);
|
||||||
|
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.LongLength, writer.BytesCommitted);
|
||||||
|
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(0, writer.BytesCommitted);
|
||||||
|
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(3, writer.BytesCommitted);
|
||||||
|
Assert.Equal(initialLength - 3, writer.Span.Length);
|
||||||
|
Assert.Equal(Pipe.Writer.GetMemory().Length, writer.Span.Length);
|
||||||
|
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BufferWriterCountsBytesCommitted()
|
||||||
|
{
|
||||||
|
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
|
||||||
|
|
||||||
|
writer.Write(new byte[] { 1, 2, 3 });
|
||||||
|
Assert.Equal(0, writer.BytesCommitted);
|
||||||
|
|
||||||
|
writer.Commit();
|
||||||
|
Assert.Equal(3, writer.BytesCommitted);
|
||||||
|
|
||||||
|
writer.Ensure(10);
|
||||||
|
writer.Advance(10);
|
||||||
|
Assert.Equal(3, writer.BytesCommitted);
|
||||||
|
|
||||||
|
writer.Commit();
|
||||||
|
Assert.Equal(13, writer.BytesCommitted);
|
||||||
|
|
||||||
|
Pipe.Writer.FlushAsync().GetAwaiter().GetResult();
|
||||||
|
var readResult = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
// Consuming the buffer does not change BytesCommitted
|
||||||
|
Assert.Equal(13, readResult.Buffer.Length);
|
||||||
|
Assert.Equal(13, writer.BytesCommitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(5)]
|
||||||
|
[InlineData(50)]
|
||||||
|
[InlineData(500)]
|
||||||
|
[InlineData(5000)]
|
||||||
|
[InlineData(50000)]
|
||||||
|
public void WriteLargeDataBinary(int length)
|
||||||
|
{
|
||||||
|
var data = new byte[length];
|
||||||
|
new Random(length).NextBytes(data);
|
||||||
|
|
||||||
|
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
|
||||||
|
writer.Write(data);
|
||||||
|
writer.Commit();
|
||||||
|
|
||||||
|
Assert.Equal(length, writer.BytesCommitted);
|
||||||
|
Assert.Equal(data, Read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
// 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.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal.Test
|
||||||
|
{
|
||||||
|
public class DiagnosticMemoryPoolTests: MemoryPoolTests
|
||||||
|
{
|
||||||
|
protected override MemoryPool<byte> CreatePool() => new DiagnosticMemoryPool(new SlabMemoryPool());
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoubleDisposeThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => memoryPool.Dispose());
|
||||||
|
Assert.Equal("Object is being disposed twice", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposeWithActiveBlocksThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
ExpectDisposeException(memoryPool);
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => block.Dispose());
|
||||||
|
Assert.Equal("Block is being returned to disposed pool", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoubleBlockDisposeThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
block.Dispose();
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => block.Dispose());
|
||||||
|
Assert.Equal("Block is being disposed twice", exception.Message);
|
||||||
|
|
||||||
|
ExpectDisposeAggregateException(memoryPool, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemoryOfDisposedPoolThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
|
||||||
|
ExpectDisposeException(memoryPool);
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => block.Memory);
|
||||||
|
Assert.Equal("Block is backed by disposed slab", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemoryPinOfDisposedPoolThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
var memory = block.Memory;
|
||||||
|
|
||||||
|
ExpectDisposeException(memoryPool);
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => memory.Pin());
|
||||||
|
Assert.Equal("Block is backed by disposed slab", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemorySpanOfDisposedPoolThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
var memory = block.Memory;
|
||||||
|
|
||||||
|
ExpectDisposeException(memoryPool);
|
||||||
|
|
||||||
|
var threw = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = memory.Span;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ode)
|
||||||
|
{
|
||||||
|
threw = true;
|
||||||
|
Assert.Equal("Block is backed by disposed slab", ode.Message);
|
||||||
|
}
|
||||||
|
Assert.True(threw);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemoryTryGetArrayOfDisposedPoolThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
var memory = block.Memory;
|
||||||
|
|
||||||
|
ExpectDisposeException(memoryPool);
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => MemoryMarshal.TryGetArray<byte>(memory, out _));
|
||||||
|
Assert.Equal("Block is backed by disposed slab", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemoryOfDisposedThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
|
||||||
|
block.Dispose();
|
||||||
|
|
||||||
|
var exception = Assert.Throws<ObjectDisposedException>(() => block.Memory);
|
||||||
|
Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", exception.Message);
|
||||||
|
|
||||||
|
ExpectDisposeAggregateException(memoryPool, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemoryPinOfDisposedThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
var memory = block.Memory;
|
||||||
|
|
||||||
|
block.Dispose();
|
||||||
|
|
||||||
|
var exception = Assert.Throws<ObjectDisposedException>(() => memory.Pin());
|
||||||
|
Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", exception.Message);
|
||||||
|
|
||||||
|
ExpectDisposeAggregateException(memoryPool, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemorySpanOfDisposedThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
var memory = block.Memory;
|
||||||
|
|
||||||
|
block.Dispose();
|
||||||
|
|
||||||
|
Exception exception = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = memory.Span;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException ode)
|
||||||
|
{
|
||||||
|
exception = ode;
|
||||||
|
Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", ode.Message);
|
||||||
|
}
|
||||||
|
Assert.NotNull(exception);
|
||||||
|
|
||||||
|
ExpectDisposeAggregateException(memoryPool, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemoryTryGetArrayOfDisposedThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
var memory = block.Memory;
|
||||||
|
|
||||||
|
block.Dispose();
|
||||||
|
|
||||||
|
var exception = Assert.Throws<ObjectDisposedException>(() => MemoryMarshal.TryGetArray<byte>(memory, out _));
|
||||||
|
Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", exception.Message);
|
||||||
|
|
||||||
|
ExpectDisposeAggregateException(memoryPool, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotThrowWithLateReturns()
|
||||||
|
{
|
||||||
|
var memoryPool = new DiagnosticMemoryPool(new SlabMemoryPool(), allowLateReturn: true);
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
block.Dispose();
|
||||||
|
await memoryPool.WhenAllBlocksReturnedAsync(TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ThrowsOnAccessToLateBlocks()
|
||||||
|
{
|
||||||
|
var memoryPool = new DiagnosticMemoryPool(new SlabMemoryPool(), allowLateReturn: true);
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => block.Memory);
|
||||||
|
Assert.Equal("Block is backed by disposed slab", exception.Message);
|
||||||
|
|
||||||
|
block.Dispose();
|
||||||
|
var aggregateException = await Assert.ThrowsAsync<AggregateException>(async () => await memoryPool.WhenAllBlocksReturnedAsync(TimeSpan.FromSeconds(5)));
|
||||||
|
|
||||||
|
Assert.Equal(new Exception [] { exception }, aggregateException.InnerExceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExceptionsContainStackTraceWhenEnabled()
|
||||||
|
{
|
||||||
|
var memoryPool = new DiagnosticMemoryPool(new SlabMemoryPool(), rentTracking: true);
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
|
||||||
|
ExpectDisposeException(memoryPool);
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => block.Memory);
|
||||||
|
Assert.Contains("Block is backed by disposed slab", exception.Message);
|
||||||
|
Assert.Contains("ExceptionsContainStackTraceWhenEnabled", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExpectDisposeException(MemoryPool<byte> memoryPool)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => memoryPool.Dispose());
|
||||||
|
Assert.Contains("Memory pool with active blocks is being disposed, 0 of 1 returned", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExpectDisposeAggregateException(MemoryPool<byte> memoryPool, params Exception[] inner)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<AggregateException>(() => memoryPool.Dispose());
|
||||||
|
|
||||||
|
Assert.Equal(inner, exception.InnerExceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\shared\**\*.cs" />
|
<Compile Include="..\shared\**\*.cs" />
|
||||||
|
<Compile Include="..\..\shared\Microsoft.Extensions.Buffers.Testing.Sources\*.cs" />
|
||||||
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
@ -23,7 +24,6 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
||||||
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
|
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
|
||||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Buffers.Testing.Sources" Version="$(MicrosoftExtensionsBuffersTestingSourcesPackageVersion)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal.Test
|
||||||
|
{
|
||||||
|
public abstract class MemoryPoolTests
|
||||||
|
{
|
||||||
|
protected abstract MemoryPool<byte> CreatePool();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanDisposeAfterCreation()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanDisposeAfterReturningBlock()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
block.Dispose();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanDisposeAfterPinUnpinBlock()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
block.Memory.Pin().Dispose();
|
||||||
|
block.Dispose();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LeasingFromDisposedPoolThrows()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
|
||||||
|
var exception = Assert.Throws<ObjectDisposedException>(() => memoryPool.Rent());
|
||||||
|
Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPool'.", exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void WritesNumericToAscii(ulong number)
|
public void WritesNumericToAscii(ulong number)
|
||||||
{
|
{
|
||||||
var writerBuffer = _pipe.Writer;
|
var writerBuffer = _pipe.Writer;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||||
writer.WriteNumeric(number);
|
writer.WriteNumeric(number);
|
||||||
writer.Commit();
|
writer.Commit();
|
||||||
writerBuffer.FlushAsync().GetAwaiter().GetResult();
|
writerBuffer.FlushAsync().GetAwaiter().GetResult();
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void WritesNumericAcrossSpanBoundaries(int gapSize)
|
public void WritesNumericAcrossSpanBoundaries(int gapSize)
|
||||||
{
|
{
|
||||||
var writerBuffer = _pipe.Writer;
|
var writerBuffer = _pipe.Writer;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||||
// almost fill up the first block
|
// almost fill up the first block
|
||||||
var spacer = new byte[writer.Span.Length - gapSize];
|
var spacer = new byte[writer.Span.Length - gapSize];
|
||||||
writer.Write(spacer);
|
writer.Write(spacer);
|
||||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void EncodesAsAscii(string input, byte[] expected)
|
public void EncodesAsAscii(string input, byte[] expected)
|
||||||
{
|
{
|
||||||
var pipeWriter = _pipe.Writer;
|
var pipeWriter = _pipe.Writer;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
var writer = new BufferWriter<PipeWriter>(pipeWriter);
|
||||||
writer.WriteAsciiNoValidation(input);
|
writer.WriteAsciiNoValidation(input);
|
||||||
writer.Commit();
|
writer.Commit();
|
||||||
pipeWriter.FlushAsync().GetAwaiter().GetResult();
|
pipeWriter.FlushAsync().GetAwaiter().GetResult();
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
// WriteAscii doesn't validate if characters are in the ASCII range
|
// WriteAscii doesn't validate if characters are in the ASCII range
|
||||||
// but it shouldn't produce more than one byte per character
|
// but it shouldn't produce more than one byte per character
|
||||||
var writerBuffer = _pipe.Writer;
|
var writerBuffer = _pipe.Writer;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||||
writer.WriteAsciiNoValidation(input);
|
writer.WriteAsciiNoValidation(input);
|
||||||
writer.Commit();
|
writer.Commit();
|
||||||
writerBuffer.FlushAsync().GetAwaiter().GetResult();
|
writerBuffer.FlushAsync().GetAwaiter().GetResult();
|
||||||
|
|
@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
const byte maxAscii = 0x7f;
|
const byte maxAscii = 0x7f;
|
||||||
var writerBuffer = _pipe.Writer;
|
var writerBuffer = _pipe.Writer;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||||
for (var i = 0; i < maxAscii; i++)
|
for (var i = 0; i < maxAscii; i++)
|
||||||
{
|
{
|
||||||
writer.WriteAsciiNoValidation(new string((char)i, 1));
|
writer.WriteAsciiNoValidation(new string((char)i, 1));
|
||||||
|
|
@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var testString = new string(' ', stringLength);
|
var testString = new string(' ', stringLength);
|
||||||
var writerBuffer = _pipe.Writer;
|
var writerBuffer = _pipe.Writer;
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||||
// almost fill up the first block
|
// almost fill up the first block
|
||||||
var spacer = new byte[writer.Span.Length - gapSize];
|
var spacer = new byte[writer.Span.Length - gapSize];
|
||||||
writer.Write(spacer);
|
writer.Write(spacer);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Internal.Test
|
||||||
|
{
|
||||||
|
public class SlabMemoryPoolTests: MemoryPoolTests
|
||||||
|
{
|
||||||
|
protected override MemoryPool<byte> CreatePool() => new SlabMemoryPool();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoubleDisposeWorks()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposeWithActiveBlocksWorks()
|
||||||
|
{
|
||||||
|
var memoryPool = CreatePool();
|
||||||
|
var block = memoryPool.Rent();
|
||||||
|
memoryPool.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -541,7 +541,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return true;
|
return true;
|
||||||
}}
|
}}
|
||||||
{(loop.ClassName == "HttpResponseHeaders" ? $@"
|
{(loop.ClassName == "HttpResponseHeaders" ? $@"
|
||||||
internal void CopyToFast(ref CountingBufferWriter<PipeWriter> output)
|
internal void CopyToFast(ref BufferWriter<PipeWriter> output)
|
||||||
{{
|
{{
|
||||||
var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0);
|
var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0);
|
||||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"
|
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue