diff --git a/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs b/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs index ff632bca63f8ae..99971148551116 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs @@ -492,31 +492,25 @@ private bool UpdateCacheSizeExceedsCapacity(CacheEntry entry, CacheEntry? priorE return false; } - long sizeRead = coherentState.Size; - for (int i = 0; i < 100; i++) + long delta = entry.Size; + if (priorEntry != null) { - long newSize = sizeRead + entry.Size; - if (priorEntry != null) - { - Debug.Assert(entry.Key == priorEntry.Key); - newSize -= priorEntry.Size; - } + Debug.Assert(entry.Key == priorEntry.Key); + delta -= priorEntry.Size; + } - if ((ulong)newSize > (ulong)sizeLimit) - { - // Overflow occurred, return true without updating the cache size - return true; - } + // Use Interlocked.Add (wait-free) instead of a CompareExchange retry loop + // to avoid CPU spikes under high concurrency. See https://github.com/dotnet/runtime/issues/111959 + long newSize = Interlocked.Add(ref coherentState._cacheSize, delta); - long original = Interlocked.CompareExchange(ref coherentState._cacheSize, newSize, sizeRead); - if (sizeRead == original) - { - return false; - } - sizeRead = original; + if ((ulong)newSize > (ulong)sizeLimit) + { + // Exceeded capacity — roll back the optimistic addition + Interlocked.Add(ref coherentState._cacheSize, -delta); + return true; } - return true; + return false; } private int lockFlag;