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;
}
}
}