-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Memory Leak in Microsoft.Extensions.Caching.Memory when handling exceptions #42321
Comments
Tagging subscribers to this area: @eerhardt, @maryamariyan |
Design hindsight: Dispose was the wrong term, it should have been named Commit or similar. Then Dispose could have been used for general cleanup without committing.
👍 There's still some risk of an exception being thrown after the value was set, such as when setting up the change tokens, timeouts, etc.. |
+💯
Agreed, but that is more of an edge case. That won't happen when using the One other risk is that if someone is relying on the behavior of not calling |
…ptions When an exception is thrown inside MemoryCache.GetOrCreate, we are leaking CacheEntry objects. This is because they are not being Disposed properly, and the async local CacheEntryStack is growing indefinitely. The fix is to ensure the CacheEntry objects are disposed correctly. In order to do this, I set a flag to indicate whether the CacheEntry.Value has been set. If it hasn't, Disposing the CacheEntry won't add it to the cache. Fix dotnet#42321
…ptions (#42355) * Memory Leak in Microsoft.Extensions.Caching.Memory when handling exceptions When an exception is thrown inside MemoryCache.GetOrCreate, we are leaking CacheEntry objects. This is because they are not being Disposed properly, and the async local CacheEntryStack is growing indefinitely. The fix is to ensure the CacheEntry objects are disposed correctly. In order to do this, I set a flag to indicate whether the CacheEntry.Value has been set. If it hasn't, Disposing the CacheEntry won't add it to the cache. Fix #42321 * Fix another memory leak when one cache depends on another cache. The inner cache's entries will reference the outer cache entries through the ScopeLease object. Null'ing out the CacheEntry._scope field when it is disposed fixes this issue.
Reopening to back port to 5.0. |
…ptions When an exception is thrown inside MemoryCache.GetOrCreate, we are leaking CacheEntry objects. This is because they are not being Disposed properly, and the async local CacheEntryStack is growing indefinitely. The fix is to ensure the CacheEntry objects are disposed correctly. In order to do this, I set a flag to indicate whether the CacheEntry.Value has been set. If it hasn't, Disposing the CacheEntry won't add it to the cache. Fix #42321
…when handling exceptions (#42494) * Memory Leak in Microsoft.Extensions.Caching.Memory when handling exceptions When an exception is thrown inside MemoryCache.GetOrCreate, we are leaking CacheEntry objects. This is because they are not being Disposed properly, and the async local CacheEntryStack is growing indefinitely. The fix is to ensure the CacheEntry objects are disposed correctly. In order to do this, I set a flag to indicate whether the CacheEntry.Value has been set. If it hasn't, Disposing the CacheEntry won't add it to the cache. Fix #42321 * Fix another memory leak when one cache depends on another cache. The inner cache's entries will reference the outer cache entries through the ScopeLease object. Null'ing out the CacheEntry._scope field when it is disposed fixes this issue. Co-authored-by: Eric Erhardt <[email protected]>
Closing as the fix is now in the 5.0 branch. |
We are leaking objects when calling
MemoryCache.GetOrCreate
and the factory method is throwing an exception.Repro
Run the following application, take a memory snapshot, run for a while and take another snapshot
Analysis
It appears the issue is that we aren't Disposing the CacheEntry instances when an exception is being thrown:
runtime/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/MemoryCacheExtensions.cs
Lines 98 to 112 in 33dba95
The reason we aren't calling Dispose is to fix an issue that we were caching
null
when an Exception was being thrown. See aspnet/Caching#216.Disposing the CacheEntry is important because every time you create a CacheEntry, it gets stuck in an AsyncLocal "stack":
runtime/src/libraries/Microsoft.Extensions.Caching.Memory/src/CacheEntryHelper.cs
Lines 28 to 36 in 33dba95
and disposing it is what "pops" it off the stack:
runtime/src/libraries/Microsoft.Extensions.Caching.Memory/src/CacheEntryHelper.cs
Lines 50 to 63 in 33dba95
We should always be
Disposing
the entry. That way theScopeLease
object is always disposed, and the stack is cleared.However, we still need to fix the original problem: Don't cache
null
when an Exception happens. The reason this happens is because Disposing the CacheEntry is what "commits the entry into the cache". This method gets called from CacheEntry.Dispose:runtime/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Lines 111 to 234 in 33dba95
To fix this, we should set a flag indicating whether the
CacheEntry.Value
was ever set. If it wasn't set, we shouldn't be committing the value into the cache.cc @Tratcher
The text was updated successfully, but these errors were encountered: