/// <summary> /// Register a notification to occur *AFTER* a GC occurs in which the memory load changes from within the bound specified /// to outside of the bound specified. This notification will occur once. If repeated notifications are required, the notification /// must be reregistered. The notification will occur on a thread which should not be blocked. Complex processing in the notification should defer work to the threadpool. /// </summary> /// <param name="lowMemoryPercent">percent of HighMemoryLoadThreshold to use as lower bound. Must be a number >= 0 or an ArgumentOutOfRangeException will be thrown.</param> /// <param name="highMemoryPercent">percent of HighMemoryLoadThreshold use to use as lower bound. Must be a number > lowMemory or an ArgumentOutOfRangeException will be thrown. </param> /// <param name="notification">delegate to invoke when operation occurs</param>s internal static void RegisterMemoryLoadChangeNotification(float lowMemoryPercent, float highMemoryPercent, Action notification) { if (highMemoryPercent < 0 || highMemoryPercent > 1.0 || highMemoryPercent <= lowMemoryPercent) { throw new ArgumentOutOfRangeException(nameof(highMemoryPercent)); } if (lowMemoryPercent < 0) { throw new ArgumentOutOfRangeException(nameof(lowMemoryPercent)); } if (notification == null) { throw new ArgumentNullException(nameof(notification)); } lock (s_notifications) { s_notifications.Add(new MemoryLoadChangeNotification(lowMemoryPercent, highMemoryPercent, notification)); if (s_notifications.Count == 1) { Gen2GcCallback.Register(InvokeMemoryLoadChangeNotifications); } } }
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 (_freeList.TryPop(out returnBuffer)) { return; } // Lazy init, Ask that TrimFreeListIfNeeded be called on every Gen 2 GC. if (_restockSize == 0) { Gen2GcCallback.Register(Gen2GcCallbackFunc, this); } // Indicate to the trimming policy that the free list is insufficient. _moreThanFreeListNeeded = true; PinnableBufferCacheEventSource.Log.AllocateBufferFreeListEmpty(_cacheName, _notGen2.Count); // Get more buffers if needed. if (_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(_notGen2.Count != 0); int idx = _notGen2.Count - 1; if (GC.GetGeneration(_notGen2[idx]) < GC.MaxGeneration && GC.GetGeneration(_notGen2[0]) == GC.MaxGeneration) { idx = 0; } returnBuffer = _notGen2[idx]; _notGen2.RemoveAt(idx); // Remember any sub-optimal 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(_cacheName, _notGen2.Count); } // If we have a Gen1 collection, then everything on _notGen2 should have aged. Move them to the _freeList. 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 (_notGen2.Count == _restockSize / 2) { PinnableBufferCacheEventSource.Log.DebugMessage("Proactively adding more buffers to aging pool"); CreateNewBuffers(); } } } }