< Summary

Information
Class: System.Net.MultiArrayBuffer
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\Common\src\System\Net\MultiArrayBuffer.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 163
Coverable lines: 163
Total lines: 426
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 46
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
Dispose()0%660%
Discard(...)0%440%
DiscardAll()100%110%
FreeBlocks(...)0%220%
Commit(...)0%220%
EnsureAvailableSpace(...)0%220%
GrowAvailableSpace(...)0%16160%
CheckState()0%14140%

File(s)

D:\runner\runtime\src\libraries\Common\src\System\Net\MultiArrayBuffer.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6
 7namespace System.Net
 8{
 9    // Warning: Mutable struct!
 10    // The purpose of this struct is to simplify buffer management in cases where the size of the buffer may grow large 
 11    // thus making it worthwhile to add the overhead involved in managing multiple individual array allocations.
 12    // Like ArrayBuffer, this manages a sliding buffer where bytes can be added at the end and removed at the beginning.
 13    // Unlike ArrayBuffer, the buffer itself is managed using 16K blocks which are added/removed to the block list as ne
 14
 15    // 'ActiveBuffer' contains the current buffer contents; these bytes will be preserved on any call to TryEnsureAvaila
 16    // 'AvailableBuffer' contains the available bytes past the end of the current content,
 17    // and can be written to in order to add data to the end of the buffer.
 18    // Commit(byteCount) will extend the ActiveBuffer by 'byteCount' bytes into the AvailableBuffer.
 19    // Discard(byteCount) will discard 'byteCount' bytes as the beginning of the ActiveBuffer.
 20    // TryEnsureAvailableBytesUpToLimit will grow the buffer if necessary; *however*, this may invalidate
 21    // old values of 'ActiveBuffer' and 'AvailableBuffer', so they must be retrieved again.
 22
 23    internal struct MultiArrayBuffer : IDisposable
 24    {
 25        private byte[]?[]? _blocks;
 26        private uint _allocatedEnd;
 27        private uint _activeStart;
 28        private uint _availableStart;
 29
 30        // Invariants:
 31        // 0 <= _activeStart <= _availableStart <= total buffer size (i.e. _blockCount * BlockSize)
 32
 33        private const int BlockSize = 16 * 1024;
 34
 035        public MultiArrayBuffer(int initialBufferSize) : this()
 036        {
 37            // 'initialBufferSize' is ignored for now. Some callers are passing useful info here that we might want to a
 038            Debug.Assert(initialBufferSize >= 0);
 039        }
 40
 41        public void Dispose()
 042        {
 043            _activeStart = 0;
 044            _availableStart = 0;
 45
 046            if (_blocks is not null)
 047            {
 048                for (int i = 0; i < _blocks.Length; i++)
 049                {
 050                    if (_blocks[i] is byte[] toReturn)
 051                    {
 052                        _blocks[i] = null;
 053                        ArrayPool<byte>.Shared.Return(toReturn);
 054                    }
 055                }
 56
 057                _blocks = null;
 058                _allocatedEnd = 0;
 059            }
 060        }
 61
 062        public bool IsEmpty => _activeStart == _availableStart;
 63
 064        public MultiMemory ActiveMemory => new MultiMemory(_blocks, _activeStart, _availableStart - _activeStart);
 65
 066        public MultiMemory AvailableMemory => new MultiMemory(_blocks, _availableStart, _allocatedEnd - _availableStart)
 67
 68        public void Discard(int byteCount)
 069        {
 070            Debug.Assert(byteCount >= 0);
 071            Debug.Assert(byteCount <= ActiveMemory.Length, $"MultiArrayBuffer.Discard: Expected byteCount={byteCount} <=
 72
 073            if (byteCount == ActiveMemory.Length)
 074            {
 075                DiscardAll();
 076                return;
 77            }
 78
 079            CheckState();
 80
 081            uint ubyteCount = (uint)byteCount;
 82
 083            uint oldStartBlock = _activeStart / BlockSize;
 084            _activeStart += ubyteCount;
 085            uint newStartBlock = _activeStart / BlockSize;
 86
 087            FreeBlocks(oldStartBlock, newStartBlock);
 88
 089            CheckState();
 090        }
 91
 92        public void DiscardAll()
 093        {
 094            CheckState();
 95
 096            uint firstAllocatedBlock = _activeStart / BlockSize;
 097            uint firstUnallocatedBlock = _allocatedEnd / BlockSize;
 098            FreeBlocks(firstAllocatedBlock, firstUnallocatedBlock);
 99
 0100            _activeStart = _availableStart = _allocatedEnd = 0;
 101
 0102            CheckState();
 103
 0104        }
 105
 106        private void FreeBlocks(uint startBlock, uint endBlock)
 0107        {
 0108            byte[]?[] blocks = _blocks!;
 0109            for (uint i = startBlock; i < endBlock; i++)
 0110            {
 0111                byte[]? toReturn = blocks[i];
 0112                Debug.Assert(toReturn is not null);
 0113                blocks[i] = null;
 0114                ArrayPool<byte>.Shared.Return(toReturn);
 0115            }
 0116        }
 117
 118        public void Commit(int byteCount)
 0119        {
 0120            Debug.Assert(byteCount >= 0);
 0121            Debug.Assert(byteCount <= AvailableMemory.Length, $"MultiArrayBuffer.Commit: Expected byteCount={byteCount} 
 122
 0123            uint ubyteCount = (uint)byteCount;
 124
 0125            _availableStart += ubyteCount;
 0126        }
 127
 128        public void EnsureAvailableSpaceUpToLimit(int byteCount, int limit)
 129        {
 130            Debug.Assert(byteCount >= 0);
 131            Debug.Assert(limit >= 0);
 132
 133            if (ActiveMemory.Length >= limit)
 134            {
 135                // Already past limit. Do nothing.
 136                return;
 137            }
 138
 139            // Enforce the limit.
 140            byteCount = Math.Min(byteCount, limit - ActiveMemory.Length);
 141
 142            EnsureAvailableSpace(byteCount);
 143        }
 144
 145        public void EnsureAvailableSpace(int byteCount)
 0146        {
 0147            Debug.Assert(byteCount >= 0);
 148
 0149            if (byteCount > AvailableMemory.Length)
 0150            {
 0151                GrowAvailableSpace(byteCount);
 0152            }
 0153        }
 154
 155        public void GrowAvailableSpace(int byteCount)
 0156        {
 0157            Debug.Assert(byteCount > AvailableMemory.Length);
 158
 0159            CheckState();
 160
 0161            uint ubyteCount = (uint)byteCount;
 162
 0163            uint newBytesNeeded = ubyteCount - (uint)AvailableMemory.Length;
 0164            uint newBlocksNeeded = (newBytesNeeded + BlockSize - 1) / BlockSize;
 165
 166            // Ensure we have enough space in the block array for the new blocks needed.
 0167            if (_blocks is null)
 0168            {
 0169                Debug.Assert(_allocatedEnd == 0);
 0170                Debug.Assert(_activeStart == 0);
 0171                Debug.Assert(_availableStart == 0);
 172
 0173                int blockArraySize = 4;
 0174                while (blockArraySize < newBlocksNeeded)
 0175                {
 0176                    blockArraySize *= 2;
 0177                }
 178
 0179                _blocks = new byte[]?[blockArraySize];
 0180            }
 181            else
 0182            {
 0183                Debug.Assert(_allocatedEnd % BlockSize == 0);
 0184                Debug.Assert(_allocatedEnd <= _blocks.Length * BlockSize);
 185
 0186                uint allocatedBlocks = _allocatedEnd / BlockSize;
 0187                uint blockArraySize = (uint)_blocks.Length;
 0188                if (allocatedBlocks + newBlocksNeeded > blockArraySize)
 0189                {
 190                    // Not enough room in current block array.
 0191                    uint unusedInitialBlocks = _activeStart / BlockSize;
 0192                    uint usedBlocks = (allocatedBlocks - unusedInitialBlocks);
 0193                    uint blocksNeeded = usedBlocks + newBlocksNeeded;
 0194                    if (blocksNeeded > blockArraySize)
 0195                    {
 196                        // Need to allocate a new array and copy.
 0197                        while (blockArraySize < blocksNeeded)
 0198                        {
 0199                            blockArraySize *= 2;
 0200                        }
 201
 0202                        byte[]?[] newBlockArray = new byte[]?[blockArraySize];
 0203                        _blocks.AsSpan((int)unusedInitialBlocks, (int)usedBlocks).CopyTo(newBlockArray);
 0204                        _blocks = newBlockArray;
 0205                    }
 206                    else
 0207                    {
 208                        // We can shift the array down to make enough space
 0209                        _blocks.AsSpan((int)unusedInitialBlocks, (int)usedBlocks).CopyTo(_blocks);
 210
 211                        // Null out the part of the array left over from the shift, so that we aren't holding references
 0212                        _blocks.AsSpan((int)usedBlocks, (int)unusedInitialBlocks).Clear();
 0213                    }
 214
 0215                    uint shift = unusedInitialBlocks * BlockSize;
 0216                    _allocatedEnd -= shift;
 0217                    _activeStart -= shift;
 0218                    _availableStart -= shift;
 219
 0220                    Debug.Assert(_activeStart / BlockSize == 0, $"Start is not in first block after move or resize?? _ac
 0221                }
 0222            }
 223
 224            // Allocate new blocks
 0225            Debug.Assert(_allocatedEnd % BlockSize == 0);
 0226            uint allocatedBlockCount = _allocatedEnd / BlockSize;
 0227            Debug.Assert(allocatedBlockCount == 0 || _blocks[allocatedBlockCount - 1] is not null);
 0228            for (uint i = 0; i < newBlocksNeeded; i++)
 0229            {
 0230                Debug.Assert(_blocks[allocatedBlockCount] is null);
 0231                _blocks[allocatedBlockCount++] = ArrayPool<byte>.Shared.Rent(BlockSize);
 0232            }
 233
 0234            _allocatedEnd = allocatedBlockCount * BlockSize;
 235
 236            // After all of that, we should have enough available memory now
 0237            Debug.Assert(byteCount <= AvailableMemory.Length);
 238
 0239            CheckState();
 0240        }
 241
 242        [Conditional("DEBUG")]
 243        private void CheckState()
 0244        {
 0245            if (_blocks == null)
 0246            {
 0247                Debug.Assert(_activeStart == 0);
 0248                Debug.Assert(_availableStart == 0);
 0249                Debug.Assert(_allocatedEnd == 0);
 0250            }
 251            else
 0252            {
 0253                Debug.Assert(_activeStart <= _availableStart);
 0254                Debug.Assert(_availableStart <= _allocatedEnd);
 0255                Debug.Assert(_allocatedEnd <= _blocks.Length * BlockSize);
 256
 0257                Debug.Assert(_allocatedEnd % BlockSize == 0, $"_allocatedEnd={_allocatedEnd} not at block boundary?");
 258
 0259                uint firstAllocatedBlock = _activeStart / BlockSize;
 0260                uint firstUnallocatedBlock = _allocatedEnd / BlockSize;
 261
 0262                for (uint i = 0; i < firstAllocatedBlock; i++)
 0263                {
 0264                    Debug.Assert(_blocks[i] is null);
 0265                }
 266
 0267                for (uint i = firstAllocatedBlock; i < firstUnallocatedBlock; i++)
 0268                {
 0269                    Debug.Assert(_blocks[i] is not null);
 0270                }
 271
 0272                for (uint i = firstUnallocatedBlock; i < _blocks.Length; i++)
 0273                {
 0274                    Debug.Assert(_blocks[i] is null);
 0275                }
 276
 0277                if (_activeStart == _availableStart)
 0278                {
 0279                    Debug.Assert(_activeStart == 0, $"No active bytes but _activeStart={_activeStart}");
 0280                }
 0281            }
 0282        }
 283    }
 284
 285    // This is a Memory-like struct for handling multi-array segments from MultiArrayBuffer above.
 286    // It supports standard Span/Memory operations like indexing, Slice, Length, etc
 287    // It also supports CopyTo/CopyFrom Span<byte>
 288
 289    internal readonly struct MultiMemory
 290    {
 291        private readonly byte[]?[]? _blocks;
 292        private readonly uint _start;
 293        private readonly uint _length;
 294
 295        private const int BlockSize = 16 * 1024;
 296
 297        internal MultiMemory(byte[]?[]? blocks, uint start, uint length)
 298        {
 299            if (length == 0)
 300            {
 301                _blocks = null;
 302                _start = 0;
 303                _length = 0;
 304            }
 305            else
 306            {
 307                Debug.Assert(blocks is not null);
 308                Debug.Assert(start <= int.MaxValue);
 309                Debug.Assert(length <= int.MaxValue);
 310                Debug.Assert(start + length <= blocks.Length * BlockSize);
 311
 312                _blocks = blocks;
 313                _start = start;
 314                _length = length;
 315            }
 316        }
 317
 318        private static uint GetBlockIndex(uint offset) => offset / BlockSize;
 319        private static uint GetOffsetInBlock(uint offset) => offset % BlockSize;
 320
 321        public bool IsEmpty => _length == 0;
 322
 323        public int Length => (int)_length;
 324
 325        public ref byte this[int index]
 326        {
 327            get
 328            {
 329                uint uindex = (uint)index;
 330                if (uindex >= _length)
 331                {
 332                    throw new IndexOutOfRangeException();
 333                }
 334
 335                uint offset = _start + uindex;
 336                return ref _blocks![GetBlockIndex(offset)]![GetOffsetInBlock(offset)];
 337            }
 338        }
 339
 340        public int BlockCount => (int)(GetBlockIndex(_start + _length + (BlockSize - 1)) - GetBlockIndex(_start));
 341
 342        public Memory<byte> GetBlock(int blockIndex)
 343        {
 344            if ((uint)blockIndex >= BlockCount)
 345            {
 346                throw new IndexOutOfRangeException();
 347            }
 348
 349            Debug.Assert(_length > 0, "Length should never be 0 here because BlockCount would be 0");
 350            Debug.Assert(_blocks is not null);
 351
 352            uint startInBlock = (blockIndex == 0 ? GetOffsetInBlock(_start) : 0);
 353            uint endInBlock = (blockIndex == BlockCount - 1 ? GetOffsetInBlock(_start + _length - 1) + 1 : BlockSize);
 354
 355            Debug.Assert(0 <= startInBlock, $"Invalid startInBlock={startInBlock}. blockIndex={blockIndex}, _blocks.Leng
 356            Debug.Assert(startInBlock < endInBlock, $"Invalid startInBlock={startInBlock}, endInBlock={endInBlock}. bloc
 357            Debug.Assert(endInBlock <= BlockSize, $"Invalid endInBlock={endInBlock}. blockIndex={blockIndex}, _blocks.Le
 358
 359            return new Memory<byte>(_blocks[GetBlockIndex(_start) + blockIndex], (int)startInBlock, (int)(endInBlock - s
 360        }
 361
 362        public MultiMemory Slice(int start)
 363        {
 364            uint ustart = (uint)start;
 365            if (ustart > _length)
 366            {
 367                throw new IndexOutOfRangeException();
 368            }
 369
 370            return new MultiMemory(_blocks, _start + ustart, _length - ustart);
 371        }
 372
 373        public MultiMemory Slice(int start, int length)
 374        {
 375            uint ustart = (uint)start;
 376            uint ulength = (uint)length;
 377            if (ustart > _length || ulength > _length - ustart)
 378            {
 379                throw new IndexOutOfRangeException();
 380            }
 381
 382            return new MultiMemory(_blocks, _start + ustart, ulength);
 383        }
 384
 385        public void CopyTo(Span<byte> destination)
 386        {
 387            if (destination.Length < _length)
 388            {
 389                throw new ArgumentOutOfRangeException(nameof(destination));
 390            }
 391
 392            int blockCount = BlockCount;
 393            for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
 394            {
 395                Memory<byte> block = GetBlock(blockIndex);
 396                block.Span.CopyTo(destination);
 397                destination = destination.Slice(block.Length);
 398            }
 399        }
 400
 401        public void CopyFrom(ReadOnlySpan<byte> source)
 402        {
 403            if (_length < source.Length)
 404            {
 405                throw new ArgumentOutOfRangeException(nameof(source));
 406            }
 407
 408            int blockCount = BlockCount;
 409            for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
 410            {
 411                Memory<byte> block = GetBlock(blockIndex);
 412
 413                if (source.Length <= block.Length)
 414                {
 415                    source.CopyTo(block.Span);
 416                    break;
 417                }
 418
 419                source.Slice(0, block.Length).CopyTo(block.Span);
 420                source = source.Slice(block.Length);
 421            }
 422        }
 423
 424        public static MultiMemory Empty => default;
 425    }
 426}