< Summary

Information
Class: System.Net.MultiMemory
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: 70
Coverable lines: 70
Total lines: 426
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 28
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(...)0%220%
GetBlockIndex(...)100%110%
GetOffsetInBlock(...)100%110%
GetBlock(...)0%12120%
Slice(...)0%440%
CopyTo(...)0%440%
CopyFrom(...)0%660%

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
 35        public MultiArrayBuffer(int initialBufferSize) : this()
 36        {
 37            // 'initialBufferSize' is ignored for now. Some callers are passing useful info here that we might want to a
 38            Debug.Assert(initialBufferSize >= 0);
 39        }
 40
 41        public void Dispose()
 42        {
 43            _activeStart = 0;
 44            _availableStart = 0;
 45
 46            if (_blocks is not null)
 47            {
 48                for (int i = 0; i < _blocks.Length; i++)
 49                {
 50                    if (_blocks[i] is byte[] toReturn)
 51                    {
 52                        _blocks[i] = null;
 53                        ArrayPool<byte>.Shared.Return(toReturn);
 54                    }
 55                }
 56
 57                _blocks = null;
 58                _allocatedEnd = 0;
 59            }
 60        }
 61
 62        public bool IsEmpty => _activeStart == _availableStart;
 63
 64        public MultiMemory ActiveMemory => new MultiMemory(_blocks, _activeStart, _availableStart - _activeStart);
 65
 66        public MultiMemory AvailableMemory => new MultiMemory(_blocks, _availableStart, _allocatedEnd - _availableStart)
 67
 68        public void Discard(int byteCount)
 69        {
 70            Debug.Assert(byteCount >= 0);
 71            Debug.Assert(byteCount <= ActiveMemory.Length, $"MultiArrayBuffer.Discard: Expected byteCount={byteCount} <=
 72
 73            if (byteCount == ActiveMemory.Length)
 74            {
 75                DiscardAll();
 76                return;
 77            }
 78
 79            CheckState();
 80
 81            uint ubyteCount = (uint)byteCount;
 82
 83            uint oldStartBlock = _activeStart / BlockSize;
 84            _activeStart += ubyteCount;
 85            uint newStartBlock = _activeStart / BlockSize;
 86
 87            FreeBlocks(oldStartBlock, newStartBlock);
 88
 89            CheckState();
 90        }
 91
 92        public void DiscardAll()
 93        {
 94            CheckState();
 95
 96            uint firstAllocatedBlock = _activeStart / BlockSize;
 97            uint firstUnallocatedBlock = _allocatedEnd / BlockSize;
 98            FreeBlocks(firstAllocatedBlock, firstUnallocatedBlock);
 99
 100            _activeStart = _availableStart = _allocatedEnd = 0;
 101
 102            CheckState();
 103
 104        }
 105
 106        private void FreeBlocks(uint startBlock, uint endBlock)
 107        {
 108            byte[]?[] blocks = _blocks!;
 109            for (uint i = startBlock; i < endBlock; i++)
 110            {
 111                byte[]? toReturn = blocks[i];
 112                Debug.Assert(toReturn is not null);
 113                blocks[i] = null;
 114                ArrayPool<byte>.Shared.Return(toReturn);
 115            }
 116        }
 117
 118        public void Commit(int byteCount)
 119        {
 120            Debug.Assert(byteCount >= 0);
 121            Debug.Assert(byteCount <= AvailableMemory.Length, $"MultiArrayBuffer.Commit: Expected byteCount={byteCount} 
 122
 123            uint ubyteCount = (uint)byteCount;
 124
 125            _availableStart += ubyteCount;
 126        }
 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)
 146        {
 147            Debug.Assert(byteCount >= 0);
 148
 149            if (byteCount > AvailableMemory.Length)
 150            {
 151                GrowAvailableSpace(byteCount);
 152            }
 153        }
 154
 155        public void GrowAvailableSpace(int byteCount)
 156        {
 157            Debug.Assert(byteCount > AvailableMemory.Length);
 158
 159            CheckState();
 160
 161            uint ubyteCount = (uint)byteCount;
 162
 163            uint newBytesNeeded = ubyteCount - (uint)AvailableMemory.Length;
 164            uint newBlocksNeeded = (newBytesNeeded + BlockSize - 1) / BlockSize;
 165
 166            // Ensure we have enough space in the block array for the new blocks needed.
 167            if (_blocks is null)
 168            {
 169                Debug.Assert(_allocatedEnd == 0);
 170                Debug.Assert(_activeStart == 0);
 171                Debug.Assert(_availableStart == 0);
 172
 173                int blockArraySize = 4;
 174                while (blockArraySize < newBlocksNeeded)
 175                {
 176                    blockArraySize *= 2;
 177                }
 178
 179                _blocks = new byte[]?[blockArraySize];
 180            }
 181            else
 182            {
 183                Debug.Assert(_allocatedEnd % BlockSize == 0);
 184                Debug.Assert(_allocatedEnd <= _blocks.Length * BlockSize);
 185
 186                uint allocatedBlocks = _allocatedEnd / BlockSize;
 187                uint blockArraySize = (uint)_blocks.Length;
 188                if (allocatedBlocks + newBlocksNeeded > blockArraySize)
 189                {
 190                    // Not enough room in current block array.
 191                    uint unusedInitialBlocks = _activeStart / BlockSize;
 192                    uint usedBlocks = (allocatedBlocks - unusedInitialBlocks);
 193                    uint blocksNeeded = usedBlocks + newBlocksNeeded;
 194                    if (blocksNeeded > blockArraySize)
 195                    {
 196                        // Need to allocate a new array and copy.
 197                        while (blockArraySize < blocksNeeded)
 198                        {
 199                            blockArraySize *= 2;
 200                        }
 201
 202                        byte[]?[] newBlockArray = new byte[]?[blockArraySize];
 203                        _blocks.AsSpan((int)unusedInitialBlocks, (int)usedBlocks).CopyTo(newBlockArray);
 204                        _blocks = newBlockArray;
 205                    }
 206                    else
 207                    {
 208                        // We can shift the array down to make enough space
 209                        _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
 212                        _blocks.AsSpan((int)usedBlocks, (int)unusedInitialBlocks).Clear();
 213                    }
 214
 215                    uint shift = unusedInitialBlocks * BlockSize;
 216                    _allocatedEnd -= shift;
 217                    _activeStart -= shift;
 218                    _availableStart -= shift;
 219
 220                    Debug.Assert(_activeStart / BlockSize == 0, $"Start is not in first block after move or resize?? _ac
 221                }
 222            }
 223
 224            // Allocate new blocks
 225            Debug.Assert(_allocatedEnd % BlockSize == 0);
 226            uint allocatedBlockCount = _allocatedEnd / BlockSize;
 227            Debug.Assert(allocatedBlockCount == 0 || _blocks[allocatedBlockCount - 1] is not null);
 228            for (uint i = 0; i < newBlocksNeeded; i++)
 229            {
 230                Debug.Assert(_blocks[allocatedBlockCount] is null);
 231                _blocks[allocatedBlockCount++] = ArrayPool<byte>.Shared.Rent(BlockSize);
 232            }
 233
 234            _allocatedEnd = allocatedBlockCount * BlockSize;
 235
 236            // After all of that, we should have enough available memory now
 237            Debug.Assert(byteCount <= AvailableMemory.Length);
 238
 239            CheckState();
 240        }
 241
 242        [Conditional("DEBUG")]
 243        private void CheckState()
 244        {
 245            if (_blocks == null)
 246            {
 247                Debug.Assert(_activeStart == 0);
 248                Debug.Assert(_availableStart == 0);
 249                Debug.Assert(_allocatedEnd == 0);
 250            }
 251            else
 252            {
 253                Debug.Assert(_activeStart <= _availableStart);
 254                Debug.Assert(_availableStart <= _allocatedEnd);
 255                Debug.Assert(_allocatedEnd <= _blocks.Length * BlockSize);
 256
 257                Debug.Assert(_allocatedEnd % BlockSize == 0, $"_allocatedEnd={_allocatedEnd} not at block boundary?");
 258
 259                uint firstAllocatedBlock = _activeStart / BlockSize;
 260                uint firstUnallocatedBlock = _allocatedEnd / BlockSize;
 261
 262                for (uint i = 0; i < firstAllocatedBlock; i++)
 263                {
 264                    Debug.Assert(_blocks[i] is null);
 265                }
 266
 267                for (uint i = firstAllocatedBlock; i < firstUnallocatedBlock; i++)
 268                {
 269                    Debug.Assert(_blocks[i] is not null);
 270                }
 271
 272                for (uint i = firstUnallocatedBlock; i < _blocks.Length; i++)
 273                {
 274                    Debug.Assert(_blocks[i] is null);
 275                }
 276
 277                if (_activeStart == _availableStart)
 278                {
 279                    Debug.Assert(_activeStart == 0, $"No active bytes but _activeStart={_activeStart}");
 280                }
 281            }
 282        }
 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)
 0298        {
 0299            if (length == 0)
 0300            {
 0301                _blocks = null;
 0302                _start = 0;
 0303                _length = 0;
 0304            }
 305            else
 0306            {
 0307                Debug.Assert(blocks is not null);
 0308                Debug.Assert(start <= int.MaxValue);
 0309                Debug.Assert(length <= int.MaxValue);
 0310                Debug.Assert(start + length <= blocks.Length * BlockSize);
 311
 0312                _blocks = blocks;
 0313                _start = start;
 0314                _length = length;
 0315            }
 0316        }
 317
 0318        private static uint GetBlockIndex(uint offset) => offset / BlockSize;
 0319        private static uint GetOffsetInBlock(uint offset) => offset % BlockSize;
 320
 321        public bool IsEmpty => _length == 0;
 322
 0323        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
 0340        public int BlockCount => (int)(GetBlockIndex(_start + _length + (BlockSize - 1)) - GetBlockIndex(_start));
 341
 342        public Memory<byte> GetBlock(int blockIndex)
 0343        {
 0344            if ((uint)blockIndex >= BlockCount)
 0345            {
 0346                throw new IndexOutOfRangeException();
 347            }
 348
 0349            Debug.Assert(_length > 0, "Length should never be 0 here because BlockCount would be 0");
 0350            Debug.Assert(_blocks is not null);
 351
 0352            uint startInBlock = (blockIndex == 0 ? GetOffsetInBlock(_start) : 0);
 0353            uint endInBlock = (blockIndex == BlockCount - 1 ? GetOffsetInBlock(_start + _length - 1) + 1 : BlockSize);
 354
 0355            Debug.Assert(0 <= startInBlock, $"Invalid startInBlock={startInBlock}. blockIndex={blockIndex}, _blocks.Leng
 0356            Debug.Assert(startInBlock < endInBlock, $"Invalid startInBlock={startInBlock}, endInBlock={endInBlock}. bloc
 0357            Debug.Assert(endInBlock <= BlockSize, $"Invalid endInBlock={endInBlock}. blockIndex={blockIndex}, _blocks.Le
 358
 0359            return new Memory<byte>(_blocks[GetBlockIndex(_start) + blockIndex], (int)startInBlock, (int)(endInBlock - s
 0360        }
 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)
 0374        {
 0375            uint ustart = (uint)start;
 0376            uint ulength = (uint)length;
 0377            if (ustart > _length || ulength > _length - ustart)
 0378            {
 0379                throw new IndexOutOfRangeException();
 380            }
 381
 0382            return new MultiMemory(_blocks, _start + ustart, ulength);
 0383        }
 384
 385        public void CopyTo(Span<byte> destination)
 0386        {
 0387            if (destination.Length < _length)
 0388            {
 0389                throw new ArgumentOutOfRangeException(nameof(destination));
 390            }
 391
 0392            int blockCount = BlockCount;
 0393            for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
 0394            {
 0395                Memory<byte> block = GetBlock(blockIndex);
 0396                block.Span.CopyTo(destination);
 0397                destination = destination.Slice(block.Length);
 0398            }
 0399        }
 400
 401        public void CopyFrom(ReadOnlySpan<byte> source)
 0402        {
 0403            if (_length < source.Length)
 0404            {
 0405                throw new ArgumentOutOfRangeException(nameof(source));
 406            }
 407
 0408            int blockCount = BlockCount;
 0409            for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
 0410            {
 0411                Memory<byte> block = GetBlock(blockIndex);
 412
 0413                if (source.Length <= block.Length)
 0414                {
 0415                    source.CopyTo(block.Span);
 0416                    break;
 417                }
 418
 0419                source.Slice(0, block.Length).CopyTo(block.Span);
 0420                source = source.Slice(block.Length);
 0421            }
 0422        }
 423
 424        public static MultiMemory Empty => default;
 425    }
 426}