< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 95
Coverable lines: 95
Total lines: 276
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 50
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%
.ctor(...)0%660%
Rent(...)0%16160%
Return(...)0%10100%
.ctor(...)100%110%
Rent()0%880%
Return(...)0%10100%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Buffers/ConfigurableArrayPool.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.Diagnostics;
 5using System.Runtime.CompilerServices;
 6using System.Threading;
 7
 8namespace System.Buffers
 9{
 10    internal sealed partial class ConfigurableArrayPool<T> : ArrayPool<T>
 11    {
 12        /// <summary>The default maximum length of each array in the pool (2^20).</summary>
 13        private const int DefaultMaxArrayLength = 1024 * 1024;
 14        /// <summary>The default maximum number of arrays per bucket that are available for rent.</summary>
 15        private const int DefaultMaxNumberOfArraysPerBucket = 50;
 16
 17        private readonly Bucket[] _buckets;
 18
 019        internal ConfigurableArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket)
 20        {
 021        }
 22
 023        internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket)
 24        {
 025            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArrayLength);
 026            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArraysPerBucket);
 27
 28            // Our bucketing algorithm has a min length of 2^4 and a max length of 2^30.
 29            // Constrain the actual max used to those values.
 30            const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
 031            if (maxArrayLength > MaximumArrayLength)
 32            {
 033                maxArrayLength = MaximumArrayLength;
 34            }
 035            else if (maxArrayLength < MinimumArrayLength)
 36            {
 037                maxArrayLength = MinimumArrayLength;
 38            }
 39
 40            // Create the buckets.
 041            int poolId = Id;
 042            int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
 043            var buckets = new Bucket[maxBuckets + 1];
 044            for (int i = 0; i < buckets.Length; i++)
 45            {
 046                buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
 47            }
 048            _buckets = buckets;
 049        }
 50
 51        /// <summary>Gets an ID for the pool to use with events.</summary>
 052        private int Id => GetHashCode();
 53
 54        [MethodImpl(MethodImplOptions.NoInlining)]
 55        public override T[] Rent(int minimumLength)
 56        {
 57            // Arrays can't be smaller than zero.  We allow requesting zero-length arrays (even though
 58            // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
 59            // to be usable in general instead of using `new`, even for computed lengths.
 060            ArgumentOutOfRangeException.ThrowIfNegative(minimumLength);
 061            if (minimumLength == 0)
 62            {
 63                // No need for events with the empty array.  Our pool is effectively infinite
 64                // and we'll never allocate for rents and never store for returns.
 065                return [];
 66            }
 67
 068            ArrayPoolEventSource log = ArrayPoolEventSource.Log;
 69            T[]? buffer;
 70
 071            int index = Utilities.SelectBucketIndex(minimumLength);
 072            if (index < _buckets.Length)
 73            {
 74                // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the
 75                // next higher bucket and try that one, but only try at most a few buckets.
 76                const int MaxBucketsToTry = 2;
 077                int i = index;
 78                do
 79                {
 80                    // Attempt to rent from the bucket.  If we get a buffer from it, return it.
 081                    buffer = _buckets[i].Rent();
 082                    if (buffer != null)
 83                    {
 084                        if (log.IsEnabled())
 85                        {
 086                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
 87                        }
 088                        return buffer;
 89                    }
 90                }
 091                while (++i < _buckets.Length && i != index + MaxBucketsToTry);
 92
 93                // The pool was exhausted for this buffer size.  Allocate a new buffer with a size corresponding
 94                // to the appropriate bucket.
 095                buffer = new T[_buckets[index]._bufferLength];
 96            }
 97            else
 98            {
 99                // The request was for a size too large for the pool.  Allocate an array of exactly the requested length
 100                // When it's returned to the pool, we'll simply throw it away.
 0101                buffer = new T[minimumLength];
 102            }
 103
 0104            if (log.IsEnabled())
 105            {
 0106                int bufferId = buffer.GetHashCode();
 0107                log.BufferRented(bufferId, buffer.Length, Id, ArrayPoolEventSource.NoBucketId);
 0108                log.BufferAllocated(bufferId, buffer.Length, Id, ArrayPoolEventSource.NoBucketId, index >= _buckets.Leng
 0109                    ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize :
 0110                    ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
 111            }
 112
 0113            return buffer;
 114        }
 115
 116        [MethodImpl(MethodImplOptions.NoInlining)]
 117        public override void Return(T[] array, bool clearArray = false)
 118        {
 0119            ArgumentNullException.ThrowIfNull(array);
 120
 0121            if (array.Length == 0)
 122            {
 123                // Ignore empty arrays.  When a zero-length array is rented, we return a singleton
 124                // rather than actually taking a buffer out of the lowest bucket.
 0125                return;
 126            }
 127
 128            // Determine with what bucket this array length is associated
 0129            int bucket = Utilities.SelectBucketIndex(array.Length);
 130
 131            // If we can tell that the buffer was allocated, drop it. Otherwise, check if we have space in the pool
 0132            bool haveBucket = bucket < _buckets.Length;
 0133            if (haveBucket)
 134            {
 135                // Clear the array if the user requests
 0136                if (clearArray)
 137                {
 0138                    Array.Clear(array);
 139                }
 140
 141                // Return the buffer to its bucket.  In the future, we might consider having Return return false
 142                // instead of dropping a bucket, in which case we could try to return to a lower-sized bucket,
 143                // just as how in Rent we allow renting from a higher-sized bucket.
 0144                _buckets[bucket].Return(array);
 145            }
 146
 147            // Log that the buffer was returned
 0148            ArrayPoolEventSource log = ArrayPoolEventSource.Log;
 0149            if (log.IsEnabled())
 150            {
 0151                int bufferId = array.GetHashCode();
 0152                log.BufferReturned(bufferId, array.Length, Id);
 0153                if (!haveBucket)
 154                {
 0155                    log.BufferDropped(bufferId, array.Length, Id, ArrayPoolEventSource.NoBucketId, ArrayPoolEventSource.
 156                }
 157            }
 0158        }
 159
 160        /// <summary>Provides a thread-safe bucket containing buffers that can be Rent'd and Return'd.</summary>
 161        private sealed class Bucket
 162        {
 163            internal readonly int _bufferLength;
 164            private readonly T[]?[] _buffers;
 165            private readonly int _poolId;
 166
 167            private SpinLock _lock; // do not make this readonly; it's a mutable struct
 168            private int _index;
 169
 170            /// <summary>
 171            /// Creates the pool with numberOfBuffers arrays where each buffer is of bufferLength length.
 172            /// </summary>
 0173            internal Bucket(int bufferLength, int numberOfBuffers, int poolId)
 174            {
 0175                _lock = new SpinLock(Debugger.IsAttached); // only enable thread tracking if debugger is attached; it ad
 0176                _buffers = new T[numberOfBuffers][];
 0177                _bufferLength = bufferLength;
 0178                _poolId = poolId;
 0179            }
 180
 181            /// <summary>Gets an ID for the bucket to use with events.</summary>
 0182            internal int Id => GetHashCode();
 183
 184            /// <summary>Takes an array from the bucket.  If the bucket is empty, returns null.</summary>
 185            internal T[]? Rent()
 186            {
 0187                T[]?[] buffers = _buffers;
 0188                T[]? buffer = null;
 189
 190                // While holding the lock, grab whatever is at the next available index and
 191                // update the index.  We do as little work as possible while holding the spin
 192                // lock to minimize contention with other threads.  The try/finally is
 193                // necessary to properly handle thread aborts on platforms which have them.
 0194                bool lockTaken = false, allocateBuffer = false;
 195                try
 196                {
 0197                    _lock.Enter(ref lockTaken);
 198
 0199                    if (_index < buffers.Length)
 200                    {
 0201                        buffer = buffers[_index];
 0202                        buffers[_index++] = null;
 0203                        allocateBuffer = buffer == null;
 204                    }
 0205                }
 206                finally
 207                {
 0208                    if (lockTaken) _lock.Exit(false);
 0209                }
 210
 211                // While we were holding the lock, we grabbed whatever was at the next available index, if
 212                // there was one.  If we tried and if we got back null, that means we hadn't yet allocated
 213                // for that slot, in which case we should do so now.
 0214                if (allocateBuffer)
 215                {
 0216                    buffer = new T[_bufferLength];
 217
 0218                    ArrayPoolEventSource log = ArrayPoolEventSource.Log;
 0219                    if (log.IsEnabled())
 220                    {
 0221                        log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id,
 0222                            ArrayPoolEventSource.BufferAllocatedReason.Pooled);
 223                    }
 224                }
 225
 0226                return buffer;
 227            }
 228
 229            /// <summary>
 230            /// Attempts to return the buffer to the bucket.  If successful, the buffer will be stored
 231            /// in the bucket and true will be returned; otherwise, the buffer won't be stored, and false
 232            /// will be returned.
 233            /// </summary>
 234            internal void Return(T[] array)
 235            {
 236                // Check to see if the buffer is the correct size for this bucket
 0237                if (array.Length != _bufferLength)
 238                {
 0239                    throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
 240                }
 241
 242                bool returned;
 243
 244                // While holding the spin lock, if there's room available in the bucket,
 245                // put the buffer into the next available slot.  Otherwise, we just drop it.
 246                // The try/finally is necessary to properly handle thread aborts on platforms
 247                // which have them.
 0248                bool lockTaken = false;
 249                try
 250                {
 0251                    _lock.Enter(ref lockTaken);
 252
 0253                    returned = _index != 0;
 0254                    if (returned)
 255                    {
 0256                        _buffers[--_index] = array;
 257                    }
 0258                }
 259                finally
 260                {
 0261                    if (lockTaken) _lock.Exit(false);
 0262                }
 263
 0264                if (!returned)
 265                {
 0266                    ArrayPoolEventSource log = ArrayPoolEventSource.Log;
 0267                    if (log.IsEnabled())
 268                    {
 0269                        log.BufferDropped(array.GetHashCode(), _bufferLength, _poolId, Id, ArrayPoolEventSource.BufferDr
 270                    }
 271                }
 0272            }
 273        }
 274    }
 275}
 276