Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +504 to +510

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical bug: When arithmetic overflow occurs in the optimistic Interlocked.Add, the rollback cannot restore the original _cacheSize value.

Example: If _cacheSize is long.MaxValue - 10 and delta is 100, the Add overflows to long.MinValue + 89. The rollback with Interlocked.Add(ref _cacheSize, -100) makes it long.MinValue - 11, not the original long.MaxValue - 10. This corrupts the cache size tracking permanently.

The old CompareExchange loop avoided this by computing newSize locally first and only updating _cacheSize if no overflow would occur. To maintain correctness, you need to check for potential overflow BEFORE calling Interlocked.Add, similar to the old approach but without the retry loop.

Copilot uses AI. Check for mistakes.
Comment on lines +506 to +510

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optimistic approach introduces false rejections under concurrent load. When multiple threads add simultaneously, later additions see the temporarily inflated size and may incorrectly reject entries that should fit.

Example: With _cacheSize=50 and limit=100, Thread A adds 60 (making size 110), then Thread B adds 30 (seeing size 140). Thread B checks 140 > 100 and rolls back, but the entry (size 30) should have been accepted since 50+30=80 < 100.

The old CompareExchange loop avoided this by checking capacity BEFORE committing the update, ensuring entries are rejected based on the actual cache state, not temporarily inflated values. This change could increase cache miss rates under high concurrency, undermining the performance improvement goal.

Copilot uses AI. Check for mistakes.
}

return true;
return false;
}

private int lockFlag;
Expand Down
Loading