-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
While analyzing some memory leaks, while trying to reduce closures to the minimum, I may have found a bug with the extensions GetOrCreate and GetOrCreateAsync. Because the current design requires the entry to be disposed so the value would be added to the cache, it can't implement the disposable pattern as expected, by invoking the Dispose in the finalizer. The problem is that it hold references for other disposable instances and, at least the GetOrCreate methods, may have some unexpected behavior. Lets imagine the following code:
await cache.GetOrCreateAsync("potatoes", async k => {
var potatoes = // get value from database
if (potatoes == null)
thrown new Exception("Value is null");
return potatoes;
});When looking at the implementation and comments, the expected behavior is not to add the value in case of an exception, but the problem is that an entry will be created but the dispose won't be called if an exception is thrown:
Because removing the dispose now would be a breaking change (#340), my recommendation to patch this bug is to invoke the factory before creating the entry with new overloads and obsolete the current ones. This would ensure that, in case of an exception, the entry wouldn't be created and nothing would be added to the cache, as expected. Something as follows:
public static TItem GetOrCreate<TItem>(this IMemoryCache cache, object key, Func<TItem> factory, Action<ICacheEntry> config = null)
{
if (!cache.TryGetValue(key, out object result))
{
result = factory();
using(var entry = cache.CreateEntry(key))
{
entry.SetValue(result);
config?.Invoke(entry);
}
}
return (TItem)result;
}