-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
DB Context pooling with proxies resulting in memory issues in 8.0 RC2 #32267
Comments
@Ottodeklerk is this something you've seen in a real database provider as well, e.g. SQL Server or SQLite? |
I just tried the example above with the SQL server provider (8.0.0-rc.2.23480.1), and the results look the same. The issue came to our attention since our .NET 7 (SQL server based) production environment has some serious memory issues and occasional hangs when the GC is active. A lot of the proxy related objects (LazyLoader) seem to be stuck in gen2. For now, we will disable pooling as a quick workaround since it creates more performance issues than it should resolve. If you need anything else please let me know. |
/cc @ajcvickers |
Clearing the LazyLoader disposables in the DbContext service scope seems to keep the memory usage consistent and does not break pooling functionality. private bool DisposeSync(bool leaseActive, bool contextShouldBeDisposed)
{
// Dispose LazyLoader objects in the Disposables list without disposing all services required by the pooled context.
var props = _serviceScope?.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic)
.First(e => e.Name == "Disposables");
var toDispose = (props!.GetValue(_serviceScope) as List<object>);
var toDisposeCopy = toDispose!.ToList();
for (var i = toDisposeCopy.Count - 1; i >= 0; i--)
{
if (toDisposeCopy[i] is LazyLoader lazyLoader)
{
lazyLoader.Dispose();
toDispose!.Remove(lazyLoader);
}
}
if (leaseActive)
{
if (contextShouldBeDisposed)
{
_disposed = true;
_lease = DbContextLease.InactiveLease;
}
}
else if (!_disposed)
{
EntityFrameworkEventSource.Log.DbContextDisposing();
_dbContextDependencies?.InfrastructureLogger.ContextDisposed(this);
_disposed = true;
_dbContextDependencies?.StateManager.Unsubscribe(resetting: true);
_dbContextDependencies = null;
_changeTracker = null;
_database = null;
_configurationSnapshot = null;
SavingChanges = null;
SavedChanges = null;
SaveChangesFailed = null;
return true;
}
return false;
} |
Fixes #32267 The problem here is that ILazyLoader is a transient IDisposable service, which means that the service scope will keep track of instances created in the scope. However, when using context pooling, the service scope is not disposed because it is instead re-used. This means that the scope keeps getting more and more instances added, and never clears them out. The fix is to make the service not IDisposable. Instead, we create instances from our own internal factory where we keep track of the instances created. These can then be disposed and freed when the context is places back in the pool, or when the scope is disposed thus disposing the factory.
Fixes #32267 The problem here is that ILazyLoader is a transient IDisposable service, which means that the service scope will keep track of instances created in the scope. However, when using context pooling, the service scope is not disposed because it is instead re-used. This means that the scope keeps getting more and more instances added, and never clears them out. The fix is to make the service not IDisposable. Instead, we create instances from our own internal factory where we keep track of the instances created. These can then be disposed and freed when the context is places back in the pool, or when the scope is disposed thus disposing the factory.
Fixes #32267 The problem here is that ILazyLoader is a transient IDisposable service, which means that the service scope will keep track of instances created in the scope. However, when using context pooling, the service scope is not disposed because it is instead re-used. This means that the scope keeps getting more and more instances added, and never clears them out. The fix is to make the service not IDisposable. Instead, we create instances from our own internal factory where we keep track of the instances created. These can then be disposed and freed when the context is places back in the pool, or when the scope is disposed thus disposing the factory.
Fixes #32267 The problem here is that ILazyLoader is a transient IDisposable service, which means that the service scope will keep track of instances created in the scope. However, when using context pooling, the service scope is not disposed because it is instead re-used. This means that the scope keeps getting more and more instances added, and never clears them out. The fix is to make the service not IDisposable. Instead, we create instances from our own internal factory where we keep track of the instances created. These can then be disposed and freed when the context is places back in the pool, or when the scope is disposed thus disposing the factory.
DB Context pooling with proxies enabled
Issue #25486 describes that this issue should be resolved in .NET 8 RC1. When comparing .NET7 with .NET8 RC2 no differences in memory handling are noticable.
When running the code below the process quickly passes the 1GB memory mark. When disabling proxies or using adddbcontext instead of pooling the memory usage is more consistent (as it should be after disposing the scope, I assume).
Code example
Memory snapshot comparison with Pooling only

Memory snapshot comparison with Pooling and proxies enabled

EF Core version:
Database provider: InMemory
Target framework: 8.0
Operating system: Win-11
IDE: VS 2022 Preview 17.8.0 Preview 3
The text was updated successfully, but these errors were encountered: