private void TrimCacheIfOverLimit()

in src/Microsoft.Diagnostics.Runtime/src/Windows/HeapSegmentDataCache.cs [142:228]


        private void TrimCacheIfOverLimit()
        {
            if (Interlocked.Read(ref _cacheSize) < _maxSize)
                return;

            IList<(KeyValuePair<ulong, SegmentCacheEntry> CacheEntry, int LastAccessTimestamp)> entries = SnapshotNonMinSizeCacheItems();
            if (entries.Count == 0)
                return;

            // Try to cut ourselves down to about 85% of our max capacity, otherwise just hang out right at that boundary and the next entry we add we end up having to
            // scavenge again, and again, and again...
            uint requiredCutAmount = (uint)(_maxSize * 0.15);

            long desiredSize = (long)(_maxSize * 0.85);
            bool haveUpdatedSnapshot = false;

            uint cutAmount = 0;
            while (cutAmount < requiredCutAmount)
            {
                // We could also be trimming on other threads, so if collectively we have brought ourselves below 85% of our max capacity then we are done
                if (Interlocked.Read(ref _cacheSize) <= desiredSize)
                    break;

                // find the largest item of the 10% of least recently accessed (remaining) items
                uint largestSizeSeen = 0;
                int curItemIndex = (entries.Count - 1) - (int)(entries.Count * 0.10);

                if (curItemIndex < 0)
                    return;

                int removalTargetIndex = -1;
                while (curItemIndex < entries.Count)
                {
                    KeyValuePair<ulong, SegmentCacheEntry> curItem = entries[curItemIndex].CacheEntry;

                    // TODO: CurrentSize is constantly in flux, other threads are paging data in and out, so while I can read it in the below check there is no guarantee that by the time I get
                    // to the code that tries to REMOVE this item that CurrentSize hasn't changed
                    //
                    // >= so we prefer the largest item that is least recently accessed, ensuring we don't remove a segment that is being actively modified now (should
                    // never happen since we also update that segments accessed timestamp, but, defense in depth).
                    if ((curItem.Value.CurrentSize - curItem.Value.MinSize) >= largestSizeSeen)
                    {
                        largestSizeSeen = (uint)(curItem.Value.CurrentSize - curItem.Value.MinSize);
                        removalTargetIndex = curItemIndex;
                    }

                    curItemIndex++;
                }

                if (removalTargetIndex < 0)
                {
                    // If we are already below our desired size or we have already re-snapshotted one time, then just give up
                    if (haveUpdatedSnapshot || (Interlocked.Read(ref _cacheSize) <= desiredSize))
                        break;

                    // we failed to find any non MinSize entries in the last 10% of entries. Since our snapshot originally ONLY contained non-MinSize entries this likely
                    // means other threads are also trimming. Re-snapshot and try again.
                    entries = SnapshotNonMinSizeCacheItems();
                    haveUpdatedSnapshot = true;

                    if (entries.Count == 0)
                        break;

                    continue;
                }

                SegmentCacheEntry targetItem = entries[removalTargetIndex].CacheEntry.Value;

                if (HeapSegmentCacheEventSource.Instance.IsEnabled())
                    HeapSegmentCacheEventSource.Instance.PageOutDataStart();

                long sizeRemoved = targetItem.PageOutData();

                if (HeapSegmentCacheEventSource.Instance.IsEnabled())
                    HeapSegmentCacheEventSource.Instance.PageOutDataEnd(sizeRemoved);

                // Whether or not we managed to remove any memory for this item (another thread may have removed it all before we could), remove it from our list of
                // entries to consider
                entries.RemoveAt(removalTargetIndex);

                if (sizeRemoved != 0)
                {
                    Interlocked.Add(ref _cacheSize, -sizeRemoved);
                    cutAmount += (uint)sizeRemoved;
                }
            }
        }