--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+using System.Buffers;
+using System.Diagnostics;
+
+namespace System.Net
+{
+ // Warning: Mutable struct!
+ // The purpose of this struct is to simplify buffer management in cases where the size of the buffer may grow large (e.g. >64K),
+ // thus making it worthwhile to add the overhead involved in managing multiple individual array allocations.
+ // Like ArrayBuffer, this manages a sliding buffer where bytes can be added at the end and removed at the beginning.
+ // Unlike ArrayBuffer, the buffer itself is managed using 16K blocks which are added/removed to the block list as necessary.
+
+ // 'ActiveBuffer' contains the current buffer contents; these bytes will be preserved on any call to TryEnsureAvailableBytesUpToLimit.
+ // 'AvailableBuffer' contains the available bytes past the end of the current content,
+ // and can be written to in order to add data to the end of the buffer.
+ // Commit(byteCount) will extend the ActiveBuffer by 'byteCount' bytes into the AvailableBuffer.
+ // Discard(byteCount) will discard 'byteCount' bytes as the beginning of the ActiveBuffer.
+ // TryEnsureAvailableBytesUpToLimit will grow the buffer if necessary; *however*, this may invalidate
+ // old values of 'ActiveBuffer' and 'AvailableBuffer', so they must be retrieved again.
+
+ internal struct MultiArrayBuffer : IDisposable
+ {
+ private byte[]?[]? _blocks;
+ private uint _allocatedEnd;
+ private uint _activeStart;
+ private uint _availableStart;
+
+ // Invariants:
+ // 0 <= _activeStart <= _availableStart <= total buffer size (i.e. _blockCount * BlockSize)
+
+ private const int BlockSize = 16 * 1024;
+
+ public MultiArrayBuffer(int initialBufferSize) : this()
+ {
+ // 'initialBufferSize' is ignored for now. Some callers are passing useful info here that we might want to act on in the future.
+ Debug.Assert(initialBufferSize >= 0);
+ }
+
+ public void Dispose()
+ {
+ _activeStart = 0;
+ _availableStart = 0;
+
+ if (_blocks is not null)
+ {
+ for (int i = 0; i < _blocks.Length; i++)
+ {
+ if (_blocks[i] is byte[] toReturn)
+ {
+ _blocks[i] = null;
+ ArrayPool<byte>.Shared.Return(toReturn);
+ }
+ }
+
+ _blocks = null;
+ _allocatedEnd = 0;
+ }
+ }
+
+ public bool IsEmpty => _activeStart == _availableStart;
+
+ public MultiMemory ActiveMemory => new MultiMemory(_blocks, _activeStart, _availableStart - _activeStart);
+
+ public MultiMemory AvailableMemory => new MultiMemory(_blocks, _availableStart, _allocatedEnd - _availableStart);
+
+ public void Discard(int byteCount)
+ {
+ Debug.Assert(byteCount >= 0);
+ Debug.Assert(byteCount <= ActiveMemory.Length, $"MultiArrayBuffer.Discard: Expected byteCount={byteCount} <= {ActiveMemory.Length}");
+
+ if (byteCount == ActiveMemory.Length)
+ {
+ DiscardAll();
+ return;
+ }
+
+ CheckState();
+
+ uint ubyteCount = (uint)byteCount;
+
+ uint oldStartBlock = _activeStart / BlockSize;
+ _activeStart += ubyteCount;
+ uint newStartBlock = _activeStart / BlockSize;
+
+ FreeBlocks(oldStartBlock, newStartBlock);
+
+ CheckState();
+ }
+
+ public void DiscardAll()
+ {
+ CheckState();
+
+ uint firstAllocatedBlock = _activeStart / BlockSize;
+ uint firstUnallocatedBlock = _allocatedEnd / BlockSize;
+ FreeBlocks(firstAllocatedBlock, firstUnallocatedBlock);
+
+ _activeStart = _availableStart = _allocatedEnd = 0;
+
+ CheckState();
+
+ }
+
+ private void FreeBlocks(uint startBlock, uint endBlock)
+ {
+ byte[]?[] blocks = _blocks!;
+ for (uint i = startBlock; i < endBlock; i++)
+ {
+ byte[]? toReturn = blocks[i];
+ Debug.Assert(toReturn is not null);
+ blocks[i] = null;
+ ArrayPool<byte>.Shared.Return(toReturn);
+ }
+ }
+
+ public void Commit(int byteCount)
+ {
+ Debug.Assert(byteCount >= 0);
+ Debug.Assert(byteCount <= AvailableMemory.Length, $"MultiArrayBuffer.Commit: Expected byteCount={byteCount} <= {AvailableMemory.Length}");
+
+ uint ubyteCount = (uint)byteCount;
+
+ _availableStart += ubyteCount;
+ }
+
+ public void EnsureAvailableSpaceUpToLimit(int byteCount, int limit)
+ {
+ Debug.Assert(byteCount >= 0);
+ Debug.Assert(limit >= 0);
+
+ if (ActiveMemory.Length >= limit)
+ {
+ // Already past limit. Do nothing.
+ return;
+ }
+
+ // Enforce the limit.
+ byteCount = Math.Min(byteCount, limit - ActiveMemory.Length);
+
+ EnsureAvailableSpace(byteCount);
+ }
+
+ public void EnsureAvailableSpace(int byteCount)
+ {
+ Debug.Assert(byteCount >= 0);
+
+ if (byteCount > AvailableMemory.Length)
+ {
+ GrowAvailableSpace(byteCount);
+ }
+ }
+
+ public void GrowAvailableSpace(int byteCount)
+ {
+ Debug.Assert(byteCount > AvailableMemory.Length);
+
+ CheckState();
+
+ uint ubyteCount = (uint)byteCount;
+
+ uint newBytesNeeded = ubyteCount - (uint)AvailableMemory.Length;
+ uint newBlocksNeeded = (newBytesNeeded + BlockSize - 1) / BlockSize;
+
+ // Ensure we have enough space in the block array for the new blocks needed.
+ if (_blocks is null)
+ {
+ Debug.Assert(_allocatedEnd == 0);
+ Debug.Assert(_activeStart == 0);
+ Debug.Assert(_availableStart == 0);
+
+ int blockArraySize = 4;
+ while (blockArraySize < newBlocksNeeded)
+ {
+ blockArraySize *= 2;
+ }
+
+ _blocks = new byte[]?[blockArraySize];
+ }
+ else
+ {
+ Debug.Assert(_allocatedEnd % BlockSize == 0);
+ Debug.Assert(_allocatedEnd <= _blocks.Length * BlockSize);
+
+ uint allocatedBlocks = _allocatedEnd / BlockSize;
+ uint blockArraySize = (uint)_blocks.Length;
+ if (allocatedBlocks + newBlocksNeeded > blockArraySize)
+ {
+ // Not enough room in current block array.
+ uint unusedInitialBlocks = _activeStart / BlockSize;
+ uint usedBlocks = (allocatedBlocks - unusedInitialBlocks);
+ uint blocksNeeded = usedBlocks + newBlocksNeeded;
+ if (blocksNeeded > blockArraySize)
+ {
+ // Need to allocate a new array and copy.
+ while (blockArraySize < blocksNeeded)
+ {
+ blockArraySize *= 2;
+ }
+
+ byte[]?[] newBlockArray = new byte[]?[blockArraySize];
+ _blocks.AsSpan().Slice((int)unusedInitialBlocks, (int)usedBlocks).CopyTo(newBlockArray);
+ _blocks = newBlockArray;
+ }
+ else
+ {
+ // We can shift the array down to make enough space
+ _blocks.AsSpan().Slice((int)unusedInitialBlocks, (int)usedBlocks).CopyTo(_blocks);
+
+ // Null out the part of the array left over from the shift, so that we aren't holding references to those blocks.
+ _blocks.AsSpan().Slice((int)usedBlocks, (int)unusedInitialBlocks).Fill(null);
+ }
+
+ uint shift = unusedInitialBlocks * BlockSize;
+ _allocatedEnd -= shift;
+ _activeStart -= shift;
+ _availableStart -= shift;
+
+ Debug.Assert(_activeStart / BlockSize == 0, $"Start is not in first block after move or resize?? _activeStart={_activeStart}");
+ }
+ }
+
+ // Allocate new blocks
+ Debug.Assert(_allocatedEnd % BlockSize == 0);
+ uint allocatedBlockCount = _allocatedEnd / BlockSize;
+ Debug.Assert(allocatedBlockCount == 0 || _blocks[allocatedBlockCount - 1] is not null);
+ for (uint i = 0; i < newBlocksNeeded; i++)
+ {
+ Debug.Assert(_blocks[allocatedBlockCount] is null);
+ _blocks[allocatedBlockCount++] = ArrayPool<byte>.Shared.Rent(BlockSize);
+ }
+
+ _allocatedEnd = allocatedBlockCount * BlockSize;
+
+ // After all of that, we should have enough available memory now
+ Debug.Assert(byteCount <= AvailableMemory.Length);
+
+ CheckState();
+ }
+
+ [Conditional("DEBUG")]
+ private void CheckState()
+ {
+ if (_blocks == null)
+ {
+ Debug.Assert(_activeStart == 0);
+ Debug.Assert(_availableStart == 0);
+ Debug.Assert(_allocatedEnd == 0);
+ }
+ else
+ {
+ Debug.Assert(_activeStart <= _availableStart);
+ Debug.Assert(_availableStart <= _allocatedEnd);
+ Debug.Assert(_allocatedEnd <= _blocks.Length * BlockSize);
+
+ Debug.Assert(_allocatedEnd % BlockSize == 0, $"_allocatedEnd={_allocatedEnd} not at block boundary?");
+
+ uint firstAllocatedBlock = _activeStart / BlockSize;
+ uint firstUnallocatedBlock = _allocatedEnd / BlockSize;
+
+ for (uint i = 0; i < firstAllocatedBlock; i++)
+ {
+ Debug.Assert(_blocks[i] is null);
+ }
+
+ for (uint i = firstAllocatedBlock; i < firstUnallocatedBlock; i++)
+ {
+ Debug.Assert(_blocks[i] is not null);
+ }
+
+ for (uint i = firstUnallocatedBlock; i < _blocks.Length; i++)
+ {
+ Debug.Assert(_blocks[i] is null);
+ }
+
+ if (_activeStart == _availableStart)
+ {
+ Debug.Assert(_activeStart == 0, $"No active bytes but _activeStart={_activeStart}");
+ }
+ }
+ }
+ }
+
+ // This is a Memory-like struct for handling multi-array segments from MultiArrayBuffer above.
+ // It supports standard Span/Memory operations like indexing, Slice, Length, etc
+ // It also supports CopyTo/CopyFrom Span<byte>
+
+ internal readonly struct MultiMemory
+ {
+ private readonly byte[]?[]? _blocks;
+ private readonly uint _start;
+ private readonly uint _length;
+
+ private const int BlockSize = 16 * 1024;
+
+ internal MultiMemory(byte[]?[]? blocks, uint start, uint length)
+ {
+ if (length == 0)
+ {
+ _blocks = null;
+ _start = 0;
+ _length = 0;
+ }
+ else
+ {
+ Debug.Assert(blocks is not null);
+ Debug.Assert(start <= int.MaxValue);
+ Debug.Assert(length <= int.MaxValue);
+ Debug.Assert(start + length <= blocks.Length * BlockSize);
+
+ _blocks = blocks;
+ _start = start;
+ _length = length;
+ }
+ }
+
+ private static uint GetBlockIndex(uint offset) => offset / BlockSize;
+ private static uint GetOffsetInBlock(uint offset) => offset % BlockSize;
+
+ public bool IsEmpty => _length == 0;
+
+ public int Length => (int)_length;
+
+ public ref byte this[int index]
+ {
+ get
+ {
+ uint uindex = (uint)index;
+ if (uindex >= _length)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ uint offset = _start + uindex;
+ return ref _blocks![GetBlockIndex(offset)]![GetOffsetInBlock(offset)];
+ }
+ }
+
+ public int BlockCount => (int)(GetBlockIndex(_start + _length + (BlockSize - 1)) - GetBlockIndex(_start));
+
+ public Memory<byte> GetBlock(int blockIndex)
+ {
+ if ((uint)blockIndex >= BlockCount)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ Debug.Assert(_length > 0, "Length should never be 0 here because BlockCount would be 0");
+ Debug.Assert(_blocks is not null);
+
+ uint startInBlock = (blockIndex == 0 ? GetOffsetInBlock(_start) : 0);
+ uint endInBlock = (blockIndex == BlockCount - 1 ? GetOffsetInBlock(_start + _length - 1) + 1 : BlockSize);
+
+ Debug.Assert(0 <= startInBlock, $"Invalid startInBlock={startInBlock}. blockIndex={blockIndex}, _blocks.Length={_blocks.Length}, _start={_start}, _length={_length}");
+ Debug.Assert(startInBlock < endInBlock, $"Invalid startInBlock={startInBlock}, endInBlock={endInBlock}. blockIndex={blockIndex}, _blocks.Length={_blocks.Length}, _start={_start}, _length={_length}");
+ Debug.Assert(endInBlock <= BlockSize, $"Invalid endInBlock={endInBlock}. blockIndex={blockIndex}, _blocks.Length={_blocks.Length}, _start={_start}, _length={_length}");
+
+ return new Memory<byte>(_blocks[GetBlockIndex(_start) + blockIndex], (int)startInBlock, (int)(endInBlock - startInBlock));
+ }
+
+ public MultiMemory Slice(int start)
+ {
+ uint ustart = (uint)start;
+ if (ustart > _length)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ return new MultiMemory(_blocks, _start + ustart, _length - ustart);
+ }
+
+ public MultiMemory Slice(int start, int length)
+ {
+ uint ustart = (uint)start;
+ uint ulength = (uint)length;
+ if (ustart > _length || ulength > _length - ustart)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ return new MultiMemory(_blocks, _start + ustart, ulength);
+ }
+
+ public void CopyTo(Span<byte> destination)
+ {
+ if (destination.Length < _length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(destination));
+ }
+
+ int blockCount = BlockCount;
+ for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
+ {
+ Memory<byte> block = GetBlock(blockIndex);
+ block.Span.CopyTo(destination);
+ destination = destination.Slice(block.Length);
+ }
+ }
+
+ public void CopyFrom(ReadOnlySpan<byte> source)
+ {
+ if (_length < source.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(source));
+ }
+
+ int blockCount = BlockCount;
+ for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
+ {
+ Memory<byte> block = GetBlock(blockIndex);
+
+ if (source.Length <= block.Length)
+ {
+ source.CopyTo(block.Span);
+ break;
+ }
+
+ source.Slice(0, block.Length).CopyTo(block.Span);
+ source = source.Slice(block.Length);
+ }
+ }
+
+ public static MultiMemory Empty => default;
+ }
+}
{
internal sealed class StreamBuffer : IDisposable
{
- private ArrayBuffer _buffer; // mutable struct, do not make this readonly
+ private MultiArrayBuffer _buffer; // mutable struct, do not make this readonly
private readonly int _maxBufferSize;
private bool _writeEnded;
private bool _readAborted;
public StreamBuffer(int initialBufferSize = DefaultInitialBufferSize, int maxBufferSize = DefaultMaxBufferSize)
{
- _buffer = new ArrayBuffer(initialBufferSize, usePool: true);
+ _buffer = new MultiArrayBuffer(initialBufferSize);
_maxBufferSize = maxBufferSize;
_readTaskSource = new ResettableValueTaskSource();
_writeTaskSource = new ResettableValueTaskSource();
Debug.Assert(!Monitor.IsEntered(SyncObject));
lock (SyncObject)
{
- return (_writeEnded && _buffer.ActiveLength == 0);
+ return (_writeEnded && _buffer.IsEmpty);
}
}
}
return 0;
}
- return _buffer.ActiveLength;
+ return _buffer.ActiveMemory.Length;
}
}
}
throw new InvalidOperationException();
}
- return _maxBufferSize - _buffer.ActiveLength;
+ return _maxBufferSize - _buffer.ActiveMemory.Length;
}
}
}
return (false, buffer.Length);
}
- _buffer.TryEnsureAvailableSpaceUpToLimit(buffer.Length, _maxBufferSize);
+ _buffer.EnsureAvailableSpaceUpToLimit(buffer.Length, _maxBufferSize);
- int bytesWritten = Math.Min(buffer.Length, _buffer.AvailableLength);
+ int bytesWritten = Math.Min(buffer.Length, _buffer.AvailableMemory.Length);
if (bytesWritten > 0)
{
- buffer.Slice(0, bytesWritten).CopyTo(_buffer.AvailableSpan);
+ _buffer.AvailableMemory.CopyFrom(buffer.Slice(0, bytesWritten));
_buffer.Commit(bytesWritten);
_readTaskSource.SignalWaiter();
return (false, 0);
}
- if (_buffer.ActiveLength > 0)
+ if (!_buffer.IsEmpty)
{
- int bytesRead = Math.Min(buffer.Length, _buffer.ActiveLength);
- _buffer.ActiveSpan.Slice(0, bytesRead).CopyTo(buffer);
+ int bytesRead = Math.Min(buffer.Length, _buffer.ActiveMemory.Length);
+ _buffer.ActiveMemory.Slice(0, bytesRead).CopyTo(buffer);
_buffer.Discard(bytesRead);
_writeTaskSource.SignalWaiter();
}
_readAborted = true;
- if (_buffer.ActiveLength != 0)
- {
- _buffer.Discard(_buffer.ActiveLength);
- }
+ _buffer.DiscardAll();
_readTaskSource.SignalWaiter();
_writeTaskSource.SignalWaiter();
AbortRead();
EndWrite();
- _buffer.Dispose();
+ lock (SyncObject)
+ {
+ _buffer.Dispose();
+ }
}
private sealed class ResettableValueTaskSource : IValueTaskSource
<Compile Include="Tests\System\Collections\Generic\LargeArrayBuilderTests.cs" />
<Compile Include="Tests\System\IO\RowConfigReaderTests.cs" />
<Compile Include="Tests\System\Net\HttpKnownHeaderNamesTests.cs" />
+ <Compile Include="Tests\System\Net\MultiArrayBufferTests.cs" />
<Compile Include="Tests\System\Net\aspnetcore\Http2\DynamicTableTest.cs" />
<Compile Include="Tests\System\Net\aspnetcore\Http2\HPackDecoderTest.cs" />
<Compile Include="Tests\System\Net\aspnetcore\Http2\HPackIntegerTest.cs" />
<Compile Include="Tests\System\IO\ConnectedStreamsTests.cs" />
<Compile Include="Tests\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
throw new XunitException(AddOptionalUserMessage($"Expected: {actual} to be greater than or equal to {greaterThanOrEqualTo}", userMessage));
}
+ // NOTE: Consider using SequenceEqual below instead, as it will give more useful information about what
+ // the actual differences are, especially for large arrays/spans.
/// <summary>
/// Validates that the actual array is equal to the expected array. XUnit only displays the first 5 values
/// of each collection if the test fails. This doesn't display at what point or how the equality assertion failed.
}
}
+ /// <summary>
+ /// Validates that the actual span is equal to the expected span.
+ /// If this fails, determine where the differences are and create an exception with that information.
+ /// </summary>
+ /// <param name="expected">The array that <paramref name="actual"/> should be equal to.</param>
+ /// <param name="actual"></param>
+ public static void SequenceEqual<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual) where T : IEquatable<T>
+ {
+ // Use the SequenceEqual to compare the arrays for better performance. The default Assert.Equal method compares
+ // the arrays by boxing each element that is very slow for large arrays.
+ if (!expected.SequenceEqual(actual))
+ {
+ if (expected.Length != actual.Length)
+ {
+ throw new XunitException($"Expected: Span of length {expected.Length}{Environment.NewLine}Actual: Span of length {actual.Length}");
+ }
+ else
+ {
+ const int MaxDiffsToShow = 10; // arbitrary; enough to be useful, hopefully, but still manageable
+
+ int diffCount = 0;
+ string message = $"Showing first {MaxDiffsToShow} differences{Environment.NewLine}";
+ for (int i = 0; i < expected.Length; i++)
+ {
+ if (!expected[i].Equals(actual[i]))
+ {
+ diffCount++;
+
+ // Add up to 10 differences to the exception message
+ if (diffCount <= MaxDiffsToShow)
+ {
+ message += $" Position {i}: Expected: {expected[i]}, Actual: {actual[i]}{Environment.NewLine}";
+ }
+ }
+ }
+
+ message += $"Total number of differences: {diffCount} out of {expected.Length}";
+
+ throw new XunitException(message);
+ }
+ }
+ }
+
+ public static void SequenceEqual<T>(Span<T> expected, Span<T> actual) where T : IEquatable<T> => SequenceEqual((ReadOnlySpan<T>)expected, (ReadOnlySpan<T>)actual);
+
+ public static void SequenceEqual<T>(T[] expected, T[] actual) where T : IEquatable<T> => SequenceEqual(expected.AsSpan(), actual.AsSpan());
+
public static void AtLeastOneEquals<T>(T expected1, T expected2, T value)
{
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
Assert.Equal(size, stream.Seek(0, SeekOrigin.Current));
}
- AssertExtensions.Equal(expected, actual.ToArray());
+ AssertExtensions.SequenceEqual(expected, actual.ToArray());
}
[Theory]
stream.Position = 0;
byte[] actual = (byte[])expected.Clone();
Assert.Equal(actual.Length, await ReadAllAsync(ReadWriteMode.AsyncMemory, stream, actual, 0, actual.Length));
- AssertExtensions.Equal(expected, actual);
+ AssertExtensions.SequenceEqual(expected, actual);
}
[Theory]
Assert.Equal(expected.Length, stream.Position);
}
- AssertExtensions.Equal(expected.AsSpan(position).ToArray(), destination.ToArray());
+ AssertExtensions.SequenceEqual(expected.AsSpan(position).ToArray(), destination.ToArray());
}
public static IEnumerable<object[]> CopyTo_CopiesAllDataFromRightPosition_Success_MemberData()
for (int i = 0; i < Copies; i++)
{
int bytesRead = await ReadAllAsync(mode, stream, actual, 0, actual.Length);
- AssertExtensions.Equal(expected, actual);
+ AssertExtensions.SequenceEqual(expected, actual);
Array.Clear(actual, 0, actual.Length);
}
}
readerBytes[i] = (byte)r;
}
- AssertExtensions.Equal(writerBytes, readerBytes);
+ AssertExtensions.SequenceEqual(writerBytes, readerBytes);
await writes;
}
Assert.Equal(readerBytes.Length, n);
- AssertExtensions.Equal(writerBytes, readerBytes);
+ AssertExtensions.SequenceEqual(writerBytes, readerBytes);
await writes;
writeable.Dispose();
await copyTask;
- AssertExtensions.Equal(dataToCopy, results.ToArray());
+ AssertExtensions.SequenceEqual(dataToCopy, results.ToArray());
}
[OuterLoop("May take several seconds")]
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Linq;
+using Xunit;
+
+namespace Tests.System.Net
+{
+ public sealed class MultiArrayBufferTests
+ {
+ const int BlockSize = 16 * 1024;
+
+ [Fact]
+ public void BasicTest()
+ {
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.ActiveMemory.BlockCount);
+ Assert.True(buffer.AvailableMemory.IsEmpty);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+ Assert.Equal(0, buffer.AvailableMemory.BlockCount);
+
+ buffer.EnsureAvailableSpace(3);
+
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.ActiveMemory.BlockCount);
+ Assert.False(buffer.AvailableMemory.IsEmpty);
+ Assert.NotEqual(0, buffer.AvailableMemory.Length);
+ Assert.NotEqual(0, buffer.AvailableMemory.BlockCount);
+
+ int available = buffer.AvailableMemory.Length;
+ Assert.True(available >= 3);
+
+ buffer.AvailableMemory[0] = 10;
+ buffer.Commit(1);
+
+ Assert.False(buffer.IsEmpty);
+ Assert.False(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(1, buffer.ActiveMemory.Length);
+ Assert.Equal(10, buffer.ActiveMemory[0]);
+ Assert.Equal(available - 1, buffer.AvailableMemory.Length);
+ Assert.Equal(1, buffer.ActiveMemory.BlockCount);
+ Assert.Equal(10, buffer.ActiveMemory.GetBlock(0).Span[0]);
+
+ buffer.AvailableMemory[0] = 20;
+ buffer.Commit(1);
+
+ Assert.False(buffer.IsEmpty);
+ Assert.False(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(2, buffer.ActiveMemory.Length);
+ Assert.Equal(20, buffer.ActiveMemory[1]);
+ Assert.Equal(available - 2, buffer.AvailableMemory.Length);
+ Assert.InRange(buffer.ActiveMemory.BlockCount, 1, 2);
+
+ buffer.AvailableMemory[0] = 30;
+ buffer.Commit(1);
+
+ Assert.False(buffer.IsEmpty);
+ Assert.False(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(3, buffer.ActiveMemory.Length);
+ Assert.Equal(30, buffer.ActiveMemory[2]);
+ Assert.Equal(available - 3, buffer.AvailableMemory.Length);
+ Assert.InRange(buffer.ActiveMemory.BlockCount, 1, 2);
+
+ buffer.Discard(1);
+ Assert.False(buffer.IsEmpty);
+ Assert.False(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(2, buffer.ActiveMemory.Length);
+ Assert.Equal(20, buffer.ActiveMemory[0]);
+ Assert.InRange(buffer.ActiveMemory.BlockCount, 1, 2);
+ Assert.Equal(20, buffer.ActiveMemory.GetBlock(0).Span[0]);
+
+ buffer.Discard(1);
+ Assert.False(buffer.IsEmpty);
+ Assert.False(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(1, buffer.ActiveMemory.Length);
+ Assert.Equal(30, buffer.ActiveMemory[0]);
+ Assert.Equal(1, buffer.ActiveMemory.BlockCount);
+ Assert.Equal(30, buffer.ActiveMemory.GetBlock(0).Span[0]);
+
+ buffer.Discard(1);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.ActiveMemory.BlockCount);
+ }
+
+ [Fact]
+ public void AddByteByByteAndConsumeByteByByte_Success()
+ {
+ const int Size = 64 * 1024 + 1;
+
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ for (int i = 0; i < Size; i++)
+ {
+ buffer.EnsureAvailableSpace(1);
+ buffer.AvailableMemory[0] = (byte)i;
+ buffer.Commit(1);
+ }
+
+ for (int i = 0; i < Size; i++)
+ {
+ Assert.Equal((byte)i, buffer.ActiveMemory[0]);
+ buffer.Discard(1);
+ }
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ }
+
+ [Fact]
+ public void AddSeveralBytesRepeatedlyAndConsumeSeveralBytesRepeatedly_Success()
+ {
+ const int ByteCount = 7;
+ const int RepeatCount = 8 * 1024; // enough to ensure we cross several block boundaries
+
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.EnsureAvailableSpace(ByteCount);
+ for (int j = 0; j < ByteCount; j++)
+ {
+ buffer.AvailableMemory[j] = (byte)(j + 1);
+ }
+ buffer.Commit(ByteCount);
+ }
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ for (int j = 0; j < ByteCount; j++)
+ {
+ Assert.Equal(j + 1, buffer.ActiveMemory[j]);
+ }
+ buffer.Discard(ByteCount);
+ }
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ }
+
+ [Fact]
+ public void AddSeveralBytesRepeatedlyAndConsumeSeveralBytesRepeatedly_UsingSlice_Success()
+ {
+ const int ByteCount = 7;
+ const int RepeatCount = 8 * 1024; // enough to ensure we cross several block boundaries
+
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.EnsureAvailableSpace(ByteCount);
+ for (int j = 0; j < ByteCount; j++)
+ {
+ buffer.AvailableMemory.Slice(j)[0] = (byte)(j + 1);
+ }
+ buffer.Commit(ByteCount);
+ }
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ for (int j = 0; j < ByteCount; j++)
+ {
+ Assert.Equal(j + 1, buffer.ActiveMemory.Slice(j)[0]);
+ }
+ buffer.Discard(ByteCount);
+ }
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ }
+
+ [Fact]
+ public void AddSeveralBytesRepeatedlyAndConsumeSeveralBytesRepeatedly_UsingSliceWithLength_Success()
+ {
+ const int ByteCount = 7;
+ const int RepeatCount = 8 * 1024; // enough to ensure we cross several block boundaries
+
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.EnsureAvailableSpace(ByteCount);
+ for (int j = 0; j < ByteCount; j++)
+ {
+ buffer.AvailableMemory.Slice(j, ByteCount - j)[0] = (byte)(j + 1);
+ }
+ buffer.Commit(ByteCount);
+ }
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ for (int j = 0; j < ByteCount; j++)
+ {
+ Assert.Equal(j + 1, buffer.ActiveMemory.Slice(j, ByteCount - j)[0]);
+ }
+ buffer.Discard(ByteCount);
+ }
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ }
+
+ [Fact]
+ public void CopyFromRepeatedlyAndCopyToRepeatedly_Success()
+ {
+ ReadOnlySpan<byte> source = new byte[] { 1, 2, 3, 4, 5, 6, 7 }.AsSpan();
+
+ const int RepeatCount = 8 * 1024; // enough to ensure we cross several block boundaries
+
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.EnsureAvailableSpace(source.Length);
+ buffer.AvailableMemory.CopyFrom(source);
+ buffer.Commit(source.Length);
+ }
+
+ Span<byte> destination = new byte[source.Length].AsSpan();
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.ActiveMemory.Slice(0, source.Length).CopyTo(destination);
+ Assert.True(source.SequenceEqual(destination));
+ buffer.Discard(source.Length);
+ }
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ }
+
+ [Fact]
+ public void CopyFromRepeatedlyAndCopyToRepeatedly_LargeCopies_Success()
+ {
+ ReadOnlySpan<byte> source = Enumerable.Range(0, 64 * 1024 - 1).Select(x => (byte)x).ToArray().AsSpan();
+
+ const int RepeatCount = 13;
+
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.EnsureAvailableSpace(source.Length);
+ buffer.AvailableMemory.CopyFrom(source);
+ buffer.Commit(source.Length);
+ }
+
+ Span<byte> destination = new byte[source.Length].AsSpan();
+ for (int i = 0; i < RepeatCount; i++)
+ {
+ buffer.ActiveMemory.Slice(0, source.Length).CopyTo(destination);
+ Assert.True(source.SequenceEqual(destination));
+ buffer.Discard(source.Length);
+ }
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.True(buffer.IsEmpty);
+ Assert.True(buffer.ActiveMemory.IsEmpty);
+ }
+
+ [Fact]
+ public void EmptyMultiMemoryTest()
+ {
+ MultiMemory mm = MultiMemory.Empty;
+
+ Assert.Equal(0, mm.Length);
+ Assert.True(mm.IsEmpty);
+ Assert.Equal(0, mm.BlockCount);
+ Assert.Equal(0, mm.Slice(0).Length);
+ Assert.Equal(0, mm.Slice(0, 0).Length);
+
+ // These should not throw
+ mm.CopyTo(new byte[0]);
+ mm.CopyFrom(new byte[0]);
+ }
+
+ [Fact]
+ public void EnsureAvailableSpaceTest()
+ {
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(0);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(1);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(2);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(BlockSize - 1);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(BlockSize);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(BlockSize + 1);
+ Assert.Equal(BlockSize * 2, buffer.AvailableMemory.Length);
+
+ buffer.Commit(BlockSize - 1);
+ Assert.Equal(BlockSize - 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize + 1, buffer.AvailableMemory.Length);
+
+ buffer.Commit(BlockSize);
+ Assert.Equal(BlockSize * 2 - 1, buffer.ActiveMemory.Length);
+ Assert.Equal(1, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(0);
+ Assert.Equal(1, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(1);
+ Assert.Equal(1, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(2);
+ Assert.Equal(BlockSize + 1, buffer.AvailableMemory.Length);
+
+ buffer.Commit(2);
+ Assert.Equal(BlockSize * 2 + 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 1, buffer.AvailableMemory.Length);
+
+ buffer.Discard(1);
+ Assert.Equal(BlockSize * 2, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 1, buffer.AvailableMemory.Length);
+
+ buffer.Discard(1);
+ Assert.Equal(BlockSize * 2 - 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 1, buffer.AvailableMemory.Length);
+
+ // This should not free the first block
+ buffer.Discard(BlockSize - 3);
+ Assert.Equal(BlockSize + 2, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 1, buffer.AvailableMemory.Length);
+
+ // This should free the first block
+ buffer.Discard(1);
+ Assert.Equal(BlockSize + 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 1, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(BlockSize - 1);
+ Assert.Equal(BlockSize - 1, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(BlockSize);
+ Assert.Equal(BlockSize * 2 - 1, buffer.AvailableMemory.Length);
+
+ buffer.Discard(BlockSize - 1);
+ Assert.Equal(2, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 2 - 1, buffer.AvailableMemory.Length);
+
+ // This will cause shifting the block array down, but not reallocating
+ buffer.EnsureAvailableSpace(BlockSize * 2);
+ Assert.Equal(BlockSize * 3 - 1, buffer.AvailableMemory.Length);
+
+ buffer.Commit(BlockSize - 2);
+ Assert.Equal(BlockSize, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 2 + 1, buffer.AvailableMemory.Length);
+
+ buffer.Commit(1);
+ Assert.Equal(BlockSize + 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 2, buffer.AvailableMemory.Length);
+
+ buffer.Commit(1);
+ Assert.Equal(BlockSize + 2, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 2 - 1, buffer.AvailableMemory.Length);
+
+ buffer.Discard(1);
+ Assert.Equal(BlockSize + 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 2 - 1, buffer.AvailableMemory.Length);
+
+ // This will cause reallocating the block array, and dealing with an unused block in the first slot
+ buffer.EnsureAvailableSpace(BlockSize * 4);
+ Assert.Equal(BlockSize + 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 5 - 1, buffer.AvailableMemory.Length);
+
+ buffer.Discard(2);
+ Assert.Equal(BlockSize - 1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 5 - 1, buffer.AvailableMemory.Length);
+
+ buffer.Commit(1);
+ Assert.Equal(BlockSize, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 5 - 2, buffer.AvailableMemory.Length);
+
+ // This will discard all active bytes, which will reset the buffer
+ buffer.Discard(BlockSize);
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpace(2);
+ buffer.Commit(2);
+ Assert.Equal(2, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 2, buffer.AvailableMemory.Length);
+
+ buffer.Discard(1);
+ Assert.Equal(1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize - 2, buffer.AvailableMemory.Length);
+
+ // Request a very large amount of available space.
+ buffer.EnsureAvailableSpace(BlockSize * 64 + 1);
+ Assert.Equal(1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 65 - 2, buffer.AvailableMemory.Length);
+
+ buffer.DiscardAll();
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+ }
+
+ [Fact]
+ public void EnsureAvailableSpaceUpToLimitTest()
+ {
+ MultiArrayBuffer buffer = new MultiArrayBuffer(0);
+
+ Assert.Equal(0, buffer.ActiveMemory.Length);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(0, 0);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(0, 1);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(1, 0);
+ Assert.Equal(0, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(1, 1);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(1, 2);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize, 0);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize + 1, 0);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize, BlockSize);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize + 1, BlockSize);
+ Assert.Equal(BlockSize, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize + 1, BlockSize + 1);
+ Assert.Equal(BlockSize * 2, buffer.AvailableMemory.Length);
+
+ buffer.Commit(2);
+ buffer.Discard(1);
+ Assert.Equal(1, buffer.ActiveMemory.Length);
+ Assert.Equal(BlockSize * 2 - 2, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize * 2 - 2, BlockSize * 2 - 3);
+ Assert.Equal(BlockSize * 2 - 2, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize * 2 - 2, BlockSize * 2 - 2);
+ Assert.Equal(BlockSize * 2 - 2, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize * 2 - 2, BlockSize * 2 - 1);
+ Assert.Equal(BlockSize * 2 - 2, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize * 2 - 1, BlockSize * 2 - 1);
+ Assert.Equal(BlockSize * 2 - 2, buffer.AvailableMemory.Length);
+
+ buffer.EnsureAvailableSpaceUpToLimit(BlockSize * 2 - 1, BlockSize * 2);
+ Assert.Equal(BlockSize * 3 - 2, buffer.AvailableMemory.Length);
+ }
+ }
+}
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
</ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="ProductionCode\Common\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="ProductionCode\Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
</ItemGroup>
Link="Common\System\HexConverter.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs"
Link="Common\System\Net\ArrayBuffer.cs"/>
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs"
+ Link="Common\System\Net\MultiArrayBuffer.cs"/>
</ItemGroup>
<!-- SocketsHttpHandler implementation -->
<ItemGroup Condition="'$(TargetsBrowser)' != 'true'">
/// <summary>Stores any trailers received after returning the response content to the caller.</summary>
private HttpResponseHeaders? _trailers;
- private ArrayBuffer _responseBuffer; // mutable struct, do not make this readonly
+ private MultiArrayBuffer _responseBuffer; // mutable struct, do not make this readonly
private int _pendingWindowUpdate;
private CreditWaiter? _creditWaiter;
private int _availableCredit;
_responseProtocolState = ResponseProtocolState.ExpectingStatus;
- _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true);
+ _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize);
_pendingWindowUpdate = 0;
_headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024;
}
// Discard any remaining buffered response data
- if (_responseBuffer.ActiveLength != 0)
- {
- _responseBuffer.Discard(_responseBuffer.ActiveLength);
- }
+ _responseBuffer.DiscardAll();
_responseProtocolState = ResponseProtocolState.Aborted;
break;
}
- if (_responseBuffer.ActiveLength + buffer.Length > StreamWindowSize)
+ if (_responseBuffer.ActiveMemory.Length + buffer.Length > StreamWindowSize)
{
// Window size exceeded.
ThrowProtocolError(Http2ProtocolErrorCode.FlowControlError);
}
_responseBuffer.EnsureAvailableSpace(buffer.Length);
- buffer.CopyTo(_responseBuffer.AvailableSpan);
+ _responseBuffer.AvailableMemory.CopyFrom(buffer);
_responseBuffer.Commit(buffer.Length);
if (endStream)
else
{
Debug.Assert(_responseProtocolState == ResponseProtocolState.Complete);
- return (false, _responseBuffer.ActiveLength == 0);
+ return (false, _responseBuffer.IsEmpty);
}
}
}
{
CheckResponseBodyState();
- if (_responseBuffer.ActiveLength > 0)
+ if (!_responseBuffer.IsEmpty)
{
- int bytesRead = Math.Min(buffer.Length, _responseBuffer.ActiveLength);
- _responseBuffer.ActiveSpan.Slice(0, bytesRead).CopyTo(buffer);
+ MultiMemory activeBuffer = _responseBuffer.ActiveMemory;
+ int bytesRead = Math.Min(buffer.Length, activeBuffer.Length);
+ activeBuffer.Slice(0, bytesRead).CopyTo(buffer);
_responseBuffer.Discard(bytesRead);
return (false, bytesRead);
Debug.Assert(!Monitor.IsEntered(SyncObject));
lock (SyncObject)
{
- if (_responseBuffer.ActiveLength == 0 && _responseProtocolState == ResponseProtocolState.Complete)
+ if (_responseBuffer.IsEmpty && _responseProtocolState == ResponseProtocolState.Complete)
{
fullyConsumed = true;
}
Cancel();
}
- _responseBuffer.Dispose();
+ lock (SyncObject)
+ {
+ _responseBuffer.Dispose();
+ }
}
private CancellationTokenRegistration RegisterRequestBodyCancellation(CancellationToken cancellationToken) =>
Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs"
Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs"
+ Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs"
Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsAnyOS)' != 'true'">
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs" Link="Common\System\Net\Logging\NetEventSource.Common.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="Common\System\Net\StreamBuffer.cs" />
</ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonTestPath)System\Threading\Tasks\TaskTimeoutExtensions.cs" Link="TestCommon\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
</ItemGroup>
<!-- Common test files -->
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs"
Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs"
+ Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs"
Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs"
Link="ProductionCode\Common\System\IO\DelegatingStream.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs"
Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs"
+ Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs"
Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="..\..\src\System\Net\Security\TlsFrameHelper.cs"
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
</ItemGroup>
</Project>
<Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
</ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\CallTrackingStream.cs" Link="Common\System\IO\CallTrackingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\ConnectedStreams.cs" Link="Common\System\IO\ConnectedStreams.cs" />
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="ProductionCode\Common\System\Net\ArrayBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
</ItemGroup>
<ItemGroup>