-
-
Notifications
You must be signed in to change notification settings - Fork 158
EFCore based Memory Leak #450
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
Comments
Thanks for reporting this. How are your test server and fixture constructed? This might be caused by a memory leak in the test not the application. Have you observed this in an actual app? |
I used the server and fixture from the file GetTests.cs. The problem comes from the expression generation using Expression.Constant. Ef Core does not recognize those a parameters anymore (unlike Ef 6), and the EF query plan gets cached for each new parameter. The solution is to replace |
@Poltuu thanks for sharing! I'll work on a minimal test for the query execution itself. We do use UPDATE: I haven't forgotten about this issue, but tried to bring this down to some minimal tests and haven't been able to reproduce it yet. |
@bart-degreed have you seen signs of this memory-leak in action? |
Yes, I can reproduce it on master with the test below, which I temporarily added to ContentNegotiation.cs: [Fact]
public async Task Memory_Leak()
{
for (int i = 0; i < 1000; i++)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/todoItems?filter[ordinal]:eq={i}");
var response = await _fixture.Client.SendAsync(request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
int cachedQueryCount = 0;
var cache = _fixture.GetService<IMemoryCache>() as MemoryCache;
var entriesProperty = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance);
var entries = entriesProperty.GetValue(cache) as ICollection;
var items = new List<string>();
if (entries != null)
{
foreach (var item in entries)
{
var methodInfoVal = item.GetType().GetProperty("Value");
var val = methodInfoVal.GetValue(item) as ICacheEntry;
if (val?.Value == null)
{
continue;
}
var contentType = val.Value.GetType();
var gens = contentType.GenericTypeArguments;
if (gens.Length == 2 && gens[0] == typeof(QueryContext) && gens[1] == typeof(IAsyncEnumerable<TodoItem>))
{
cachedQueryCount++;
}
}
}
Assert.Equal(1, cachedQueryCount); //Except it's 1000
} But to make the problem visible, you also need to make EF Core use a shared cache due to a breaking change in EF Core 3.0 via Startup.cs: public virtual void ConfigureServices(IServiceCollection services)
{
var cacheInstance = new MemoryCache(new MemoryCacheOptions());
services.AddSingleton<IMemoryCache, MemoryCache>(x => cacheInstance);
services
.AddDbContext<AppDbContext>(options =>
{
options.UseMemoryCache(cacheInstance);
... I did not run a load test to see if there is an actual memory leak, but in any case it is inefficient usage of cache space. Each filter parameter in the query string restarts the query parse process and creates a new cache entry, because of a different constant value. By changing the constant into a parameter, the parsed query can be reused by EF Core. And probably by database servers too. |
Description
There is a memory leak, based on the way EF Core cache queries, especially queries with parameters automatically generated with such a librairy.
The following test (on GetTests.cs) shows just that, with
cachedQueryCount
being equal to 1000 instead of 1.Environment
The text was updated successfully, but these errors were encountered: