Skip to content

GetOrCreate/GetOrCreateAsync may lead to memory leaks? #36396

@gravity00

Description

@gravity00

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:

https://github.com/aspnet/Caching/blob/56447f941b39337947273476b2c366b3dffde565/src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs#L92-L121

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;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions