/// <summary> /// Schedule 'callback' to be called in the next GC. If the callback returns true it is /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. /// /// NOTE: This callback will be kept alive until either the callback function returns false, /// or the target object dies. /// </summary> public static void Register(Func <object, bool> callback, object targetObj) { // Create a unreachable object that remembers the callback function and target object. Gen2GcCallback gcCallback = new Gen2GcCallback(); gcCallback.Setup(callback, targetObj); }
/// <summary> /// Called when we don't have any buffers in our free list to give out. /// </summary> /// <returns></returns> private void Restock(out object returnBuffer) { lock (this) { // Try again after getting the lock as another thread could have just filled the free list. If we don't check // then we unnecessarily grab a new set of buffers because we think we are out. if (m_FreeList.TryPop(out returnBuffer)) { return; } // Lazy init, Ask that TrimFreeListIfNeeded be called on every Gen 2 GC. if (m_restockSize == 0) { Gen2GcCallback.Register(Gen2GcCallbackFunc, this); } // Indicate to the trimming policy that the free list is insufficent. m_moreThanFreeListNeeded = true; PinnableBufferCacheEventSource.Log.AllocateBufferFreeListEmpty(m_CacheName, m_NotGen2.Count); // Get more buffers if needed. if (m_NotGen2.Count == 0) { CreateNewBuffers(); } // We have no buffers in the aged freelist, so get one from the newer list. Try to pick the best one. // Debug.Assert(m_NotGen2.Count != 0); int idx = m_NotGen2.Count - 1; if (GC.GetGeneration(m_NotGen2[idx]) < GC.MaxGeneration && GC.GetGeneration(m_NotGen2[0]) == GC.MaxGeneration) { idx = 0; } returnBuffer = m_NotGen2[idx]; m_NotGen2.RemoveAt(idx); // Remember any sub-optimial buffer so we don't put it on the free list when it gets freed. if (PinnableBufferCacheEventSource.Log.IsEnabled() && GC.GetGeneration(returnBuffer) < GC.MaxGeneration) { PinnableBufferCacheEventSource.Log.AllocateBufferFromNotGen2(m_CacheName, m_NotGen2.Count); } // If we have a Gen1 collection, then everything on m_NotGen2 should have aged. Move them to the m_Free list. if (!AgePendingBuffers()) { // Before we could age at set of buffers, we have handed out half of them. // This implies we should be proactive about allocating more (since we will trim them if we over-allocate). if (m_NotGen2.Count == m_restockSize / 2) { PinnableBufferCacheEventSource.Log.DebugMessage("Proactively adding more buffers to aging pool"); CreateNewBuffers(); } } } }
private T[]?[] InitializeTlsBucketsAndTrimming() { Debug.Assert(t_tlsBuckets is null); T[]?[]? tlsBuckets = new T[NumBuckets][]; t_tlsBuckets = tlsBuckets; _allTlsBuckets.Add(tlsBuckets, null); if (Interlocked.Exchange(ref _trimCallbackCreated, 1) == 0) { Gen2GcCallback.Register(s => ((TlsOverPerCoreLockedStacksArrayPool <T>)s).Trim(), this); } return(tlsBuckets); }
/// <summary> /// Schedule 'callback' to be called in the next GC. If the callback returns true it is /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. /// /// NOTE: This callback will be kept alive until either the callback function returns false, /// or the target object dies. /// </summary> public static void Register(Func<object, bool> callback, object targetObj) { // Create a unreachable object that remembers the callback function and target object. Gen2GcCallback gcCallback = new Gen2GcCallback(); gcCallback.Setup(callback, targetObj); }
public override void Return(T[] array, bool clearArray = false) { if (array == null) { throw new ArgumentNullException(nameof(array)); } // Determine with what bucket this array length is associated int bucketIndex = Utilities.SelectBucketIndex(array.Length); // If we can tell that the buffer was allocated (or empty), drop it. Otherwise, check if we have space in the pool. if (bucketIndex < _buckets.Length) { // Clear the array if the user requests. if (clearArray) { Array.Clear(array, 0, array.Length); } // Check to see if the buffer is the correct size for this bucket if (array.Length != _bucketArraySizes[bucketIndex]) { throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array)); } // Write through the TLS bucket. If there weren't any buckets, create them // and store this array into it. If there were, store this into it, and // if there was a previous one there, push that to the global stack. This // helps to keep LIFO access such that the most recently pushed stack will // be in TLS and the first to be popped next. T[]?[]? tlsBuckets = t_tlsBuckets; if (tlsBuckets == null) { t_tlsBuckets = tlsBuckets = new T[NumBuckets][]; tlsBuckets[bucketIndex] = array; if (s_trimBuffers) { Debug.Assert(s_allTlsBuckets != null, "Should be non-null iff s_trimBuffers is true"); s_allTlsBuckets.Add(tlsBuckets, null); if (Interlocked.Exchange(ref _callbackCreated, 1) != 1) { Gen2GcCallback.Register(Gen2GcCallbackFunc, this); } } } else { T[]? prev = tlsBuckets[bucketIndex]; tlsBuckets[bucketIndex] = array; if (prev != null) { PerCoreLockedStacks stackBucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex); stackBucket.TryPush(prev); } } } // Log that the buffer was returned ArrayPoolEventSource log = ArrayPoolEventSource.Log; if (log.IsEnabled()) { log.BufferReturned(array.GetHashCode(), array.Length, Id); } }