-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Replace CAS retry loop with Interlocked.Add in MemoryCache size tracking #124430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
+506
to
+510
|
||
| } | ||
|
|
||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| private int lockFlag; | ||
|
|
||
There was a problem hiding this comment.
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.