Adds BufferReader and BufferWriter. Updates UOP handling. (#101)
This commit is contained in:
parent
2c0d97cd36
commit
038c3be258
98 changed files with 2458 additions and 2185 deletions
314
Projects/Server/Buffers/BufferReader.cs
Normal file
314
Projects/Server/Buffers/BufferReader.cs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
// Copyright (c) Harry Pierson. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
public ref struct BufferReader<T>
|
||||
{
|
||||
private readonly bool usingSequence;
|
||||
private readonly ReadOnlySequence<T> sequence;
|
||||
private SequencePosition currentPosition;
|
||||
private SequencePosition nextPosition;
|
||||
private bool moreData;
|
||||
private readonly long length;
|
||||
|
||||
public BufferReader(ReadOnlySpan<T> span)
|
||||
{
|
||||
usingSequence = false;
|
||||
CurrentSpanIndex = 0;
|
||||
Consumed = 0;
|
||||
sequence = default;
|
||||
currentPosition = default;
|
||||
length = span.Length;
|
||||
|
||||
CurrentSpan = span;
|
||||
nextPosition = default;
|
||||
moreData = span.Length > 0;
|
||||
}
|
||||
|
||||
public BufferReader(in ReadOnlySequence<T> sequence)
|
||||
{
|
||||
usingSequence = true;
|
||||
CurrentSpanIndex = 0;
|
||||
Consumed = 0;
|
||||
this.sequence = sequence;
|
||||
currentPosition = sequence.Start;
|
||||
length = -1;
|
||||
|
||||
var first = sequence.First.Span;
|
||||
CurrentSpan = first;
|
||||
nextPosition = sequence.GetPosition(first.Length);
|
||||
moreData = first.Length > 0;
|
||||
|
||||
if (!moreData && !sequence.IsSingleSegment)
|
||||
{
|
||||
moreData = true;
|
||||
GetNextSpan();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool End => !moreData;
|
||||
|
||||
public ReadOnlySpan<T> CurrentSpan { get; private set; }
|
||||
|
||||
public int CurrentSpanIndex { get; private set; }
|
||||
|
||||
public readonly ReadOnlySpan<T> UnreadSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CurrentSpan.Slice(CurrentSpanIndex);
|
||||
}
|
||||
|
||||
public long Consumed { get; private set; }
|
||||
|
||||
public readonly long Remaining => Length - Consumed;
|
||||
|
||||
public readonly long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
// Cast-away readonly to initialize lazy field
|
||||
Volatile.Write(ref Unsafe.AsRef(length), sequence.Length);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryPeek([MaybeNullWhen(false)] out T value)
|
||||
{
|
||||
if (moreData)
|
||||
{
|
||||
value = CurrentSpan[CurrentSpanIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryRead([MaybeNullWhen(false)] out T value)
|
||||
{
|
||||
if (End)
|
||||
{
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = CurrentSpan[CurrentSpanIndex];
|
||||
CurrentSpanIndex++;
|
||||
Consumed++;
|
||||
|
||||
if (CurrentSpanIndex >= CurrentSpan.Length)
|
||||
{
|
||||
if (usingSequence)
|
||||
GetNextSpan();
|
||||
else
|
||||
moreData = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Rewind(long count)
|
||||
{
|
||||
if ((ulong)count > (ulong)Consumed) throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
Consumed -= count;
|
||||
|
||||
if (CurrentSpanIndex >= count)
|
||||
{
|
||||
CurrentSpanIndex -= (int)count;
|
||||
moreData = true;
|
||||
}
|
||||
else if (usingSequence)
|
||||
{
|
||||
// Current segment doesn't have enough data, scan backward through segments
|
||||
RetreatToPreviousSpan(Consumed);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Rewind went past the start of the memory by {count}.", nameof(count));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void RetreatToPreviousSpan(long consumed)
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
ResetReader();
|
||||
Advance(consumed);
|
||||
}
|
||||
|
||||
private void ResetReader()
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
CurrentSpanIndex = 0;
|
||||
Consumed = 0;
|
||||
currentPosition = sequence.Start;
|
||||
nextPosition = currentPosition;
|
||||
|
||||
if (sequence.TryGet(ref nextPosition, out ReadOnlyMemory<T> memory))
|
||||
{
|
||||
moreData = true;
|
||||
|
||||
if (memory.Length == 0)
|
||||
{
|
||||
CurrentSpan = default;
|
||||
// No data in the first span, move to one with data
|
||||
GetNextSpan();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentSpan = memory.Span;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No data in any spans and at end of sequence
|
||||
moreData = false;
|
||||
CurrentSpan = default;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetNextSpan()
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
if (!sequence.IsSingleSegment)
|
||||
{
|
||||
SequencePosition previousNextPosition = nextPosition;
|
||||
while (sequence.TryGet(ref nextPosition, out ReadOnlyMemory<T> memory))
|
||||
{
|
||||
currentPosition = previousNextPosition;
|
||||
if (memory.Length > 0)
|
||||
{
|
||||
CurrentSpan = memory.Span;
|
||||
CurrentSpanIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentSpan = default;
|
||||
CurrentSpanIndex = 0;
|
||||
previousNextPosition = nextPosition;
|
||||
}
|
||||
}
|
||||
moreData = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Advance(long count)
|
||||
{
|
||||
const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000);
|
||||
if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count)
|
||||
{
|
||||
CurrentSpanIndex += (int)count;
|
||||
Consumed += count;
|
||||
}
|
||||
else if (usingSequence)
|
||||
{
|
||||
// Can't satisfy from the current span
|
||||
AdvanceToNextSpan(count);
|
||||
}
|
||||
else if (CurrentSpan.Length - CurrentSpanIndex == (int)count)
|
||||
{
|
||||
CurrentSpanIndex += (int)count;
|
||||
Consumed += count;
|
||||
moreData = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
}
|
||||
|
||||
private void AdvanceToNextSpan(long count)
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
Consumed += count;
|
||||
while (moreData)
|
||||
{
|
||||
int remaining = CurrentSpan.Length - CurrentSpanIndex;
|
||||
|
||||
if (remaining > count)
|
||||
{
|
||||
CurrentSpanIndex += (int)count;
|
||||
count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// As there may not be any further segments we need to
|
||||
// push the current index to the end of the span.
|
||||
CurrentSpanIndex += remaining;
|
||||
count -= remaining;
|
||||
Debug.Assert(count >= 0);
|
||||
|
||||
GetNextSpan();
|
||||
|
||||
if (count == 0) break;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
{
|
||||
// Not enough data left- adjust for where we actually ended and throw
|
||||
Consumed -= count;
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryCopyTo(Span<T> destination)
|
||||
{
|
||||
// This API doesn't advance to facilitate conditional advancement based on the data returned.
|
||||
// We don't provide an advance option to allow easier utilizing of stack allocated destination spans.
|
||||
// (Because we can make this method readonly we can guarantee that we won't capture the span.)
|
||||
|
||||
ReadOnlySpan<T> firstSpan = UnreadSpan;
|
||||
if (firstSpan.Length >= destination.Length)
|
||||
{
|
||||
firstSpan.Slice(0, destination.Length).CopyTo(destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not enough in the current span to satisfy the request, fall through to the slow path
|
||||
return TryCopyMultisegment(destination);
|
||||
}
|
||||
|
||||
internal readonly bool TryCopyMultisegment(Span<T> destination)
|
||||
{
|
||||
// If we don't have enough to fill the requested buffer, return false
|
||||
if (Remaining < destination.Length)
|
||||
return false;
|
||||
|
||||
ReadOnlySpan<T> firstSpan = UnreadSpan;
|
||||
Debug.Assert(firstSpan.Length < destination.Length);
|
||||
firstSpan.CopyTo(destination);
|
||||
int copied = firstSpan.Length;
|
||||
|
||||
SequencePosition next = nextPosition;
|
||||
while (sequence.TryGet(ref next, out ReadOnlyMemory<T> nextSegment))
|
||||
if (nextSegment.Length > 0)
|
||||
{
|
||||
ReadOnlySpan<T> nextSpan = nextSegment.Span;
|
||||
int toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
|
||||
nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
|
||||
copied += toCopy;
|
||||
if (copied >= destination.Length) break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Projects/Server/Buffers/BufferReaderExtensions.cs
Normal file
181
Projects/Server/Buffers/BufferReaderExtensions.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright (c) Harry Pierson. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
public static class BufferReaderExtensions
|
||||
{
|
||||
private static unsafe bool TryRead<T>(ref this BufferReader<byte> reader, out T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
if (span.Length < sizeof(T)) return TryReadMultisegment(ref reader, out value);
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
|
||||
reader.Advance(sizeof(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe bool TryReadMultisegment<T>(ref BufferReader<byte> reader, out T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
Debug.Assert(reader.UnreadSpan.Length < sizeof(T), "reader.UnreadSpan.Length < sizeof(T)");
|
||||
|
||||
// Not enough data in the current segment, try to peek for the data we need.
|
||||
T buffer = default;
|
||||
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
|
||||
|
||||
if (!reader.TryCopyTo(tempSpan))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
|
||||
reader.Advance(sizeof(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryRead(ref this BufferReader<byte> reader, out sbyte value)
|
||||
{
|
||||
if (TryRead(ref reader, out byte byteValue))
|
||||
{
|
||||
value = unchecked((sbyte)byteValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out short value) =>
|
||||
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out short value) =>
|
||||
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out short value)
|
||||
{
|
||||
if (reader.TryRead(out value))
|
||||
{
|
||||
value = BinaryPrimitives.ReverseEndianness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out ushort value)
|
||||
{
|
||||
if (TryReadLittleEndian(ref reader, out short signedvalue))
|
||||
{
|
||||
value = unchecked((ushort)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out ushort value)
|
||||
{
|
||||
if (TryReadBigEndian(ref reader, out short signedvalue))
|
||||
{
|
||||
value = unchecked((ushort)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out int value) =>
|
||||
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out int value) =>
|
||||
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out int value)
|
||||
{
|
||||
if (reader.TryRead(out value))
|
||||
{
|
||||
value = BinaryPrimitives.ReverseEndianness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out uint value)
|
||||
{
|
||||
if (TryReadLittleEndian(ref reader, out int signedvalue))
|
||||
{
|
||||
value = unchecked((uint)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out uint value)
|
||||
{
|
||||
if (TryReadBigEndian(ref reader, out int signedvalue))
|
||||
{
|
||||
value = unchecked((uint)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out long value) =>
|
||||
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out long value) =>
|
||||
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out long value)
|
||||
{
|
||||
if (reader.TryRead(out value))
|
||||
{
|
||||
value = BinaryPrimitives.ReverseEndianness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out ulong value)
|
||||
{
|
||||
if (TryReadLittleEndian(ref reader, out long signedvalue))
|
||||
{
|
||||
value = unchecked((ulong)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out ulong value)
|
||||
{
|
||||
if (TryReadBigEndian(ref reader, out long signedvalue))
|
||||
{
|
||||
value = unchecked((ulong)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Projects/Server/Buffers/BufferWriter.cs
Normal file
143
Projects/Server/Buffers/BufferWriter.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// 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;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// A fast access struct that wraps <see cref="IBufferWriter{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of element to be written.</typeparam>
|
||||
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <see cref="IBufferWriter{T}"/>.
|
||||
/// </summary>
|
||||
private T _output;
|
||||
|
||||
/// <summary>
|
||||
/// The result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)"/>, less any bytes already "consumed" with <see cref="Advance(int)"/>.
|
||||
/// Backing field for the <see cref="Span"/> property.
|
||||
/// </summary>
|
||||
private Span<byte> _span;
|
||||
|
||||
/// <summary>
|
||||
/// The number of uncommitted bytes (all the calls to <see cref="Advance(int)"/> since the last call to <see cref="Commit"/>).
|
||||
/// </summary>
|
||||
private int _buffered;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of bytes written with this writer.
|
||||
/// Backing field for the <see cref="BytesCommitted"/> property.
|
||||
/// </summary>
|
||||
private long _bytesCommitted;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BufferWriter{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="output">The <see cref="IBufferWriter{T}"/> to be wrapped.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public BufferWriter(T output)
|
||||
{
|
||||
_buffered = 0;
|
||||
_bytesCommitted = 0;
|
||||
_output = output;
|
||||
_span = output.GetSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)"/>.
|
||||
/// </summary>
|
||||
public Span<byte> Span => _span;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of bytes written with this writer.
|
||||
/// </summary>
|
||||
public long BytesCommitted => _bytesCommitted;
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="IBufferWriter{T}.Advance(int)"/> on the underlying writer
|
||||
/// with the number of uncommitted bytes.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Commit()
|
||||
{
|
||||
var buffered = _buffered;
|
||||
if (buffered > 0)
|
||||
{
|
||||
_bytesCommitted += buffered;
|
||||
_buffered = 0;
|
||||
_output.Advance(buffered);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to indicate that part of the buffer has been written to.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bytes written to.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Advance(int count)
|
||||
{
|
||||
_buffered += count;
|
||||
_span = _span.Slice(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the caller's buffer into this writer and calls <see cref="Advance(int)"/> with the length of the source buffer.
|
||||
/// </summary>
|
||||
/// <param name="source">The buffer to copy in.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (_span.Length >= source.Length)
|
||||
{
|
||||
source.CopyTo(_span);
|
||||
Advance(source.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMultiBuffer(source);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bytes that must be allocated in a single buffer.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Ensure(int count = 1)
|
||||
{
|
||||
if (_span.Length < count) EnsureMore(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a fresh span to write to, with an optional minimum size.
|
||||
/// </summary>
|
||||
/// <param name="count">The minimum size for the next requested buffer.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void EnsureMore(int count = 0)
|
||||
{
|
||||
if (_buffered > 0) Commit();
|
||||
|
||||
_span = _output.GetSpan(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer.
|
||||
/// </summary>
|
||||
/// <param name="source">The buffer to copy into this writer.</param>
|
||||
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
|
||||
{
|
||||
while (source.Length > 0)
|
||||
{
|
||||
if (_span.Length == 0) EnsureMore();
|
||||
|
||||
var writable = Math.Min(source.Length, _span.Length);
|
||||
source.Slice(0, writable).CopyTo(_span);
|
||||
source = source.Slice(writable);
|
||||
Advance(writable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to allocate and distribute re-usable blocks of memory.
|
||||
/// </summary>
|
||||
public 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> _allBlocksReturned;
|
||||
|
||||
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();
|
||||
_allBlocksReturned = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_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())
|
||||
_allBlocksReturned.SetException(CreateAccessExceptions());
|
||||
else
|
||||
_allBlocksReturned.SetResult(null);
|
||||
}
|
||||
|
||||
private AggregateException CreateAccessExceptions() => new AggregateException("Exceptions occurred while accessing blocks", _blockAccessExceptions.ToArray());
|
||||
|
||||
public async Task WhenAllBlocksReturnedAsync(TimeSpan timeout)
|
||||
{
|
||||
var task = await Task.WhenAny(_allBlocksReturned.Task, Task.Delay(timeout));
|
||||
if (task != _allBlocksReturned.Task)
|
||||
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlocksWereNotReturnedInTime(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.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>
|
||||
public 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 readonly 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 ??= _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
|||
|
||||
namespace System.Buffers
|
||||
{
|
||||
public class MemoryPoolThrowHelper
|
||||
public static class MemoryPoolThrowHelper
|
||||
{
|
||||
public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset)
|
||||
{
|
||||
|
|
@ -17,60 +17,6 @@ namespace System.Buffers
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset) =>
|
||||
(uint)offset > (uint)sourceLength ? new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset)) : 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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue