DI: Allow IDisposable services to get garbage collected as soon as they are disposed
See original GitHub issueThis idea came up in a discussion with @danroth27, @rynowak and @ajcvickers about using EF Core in Server-side Blazor applications, and specifically about how creating a large (theoretically unbounded) number of transient DbContext
instances from DI would cause memory leaks with long living DI scopes.
DI scopes maintain a list of disposable service instances resolved so that they can be safely disposed when the scope is disposed. These references cause all disposable services to stay in memory for as long as their owner scope is in memory.
As a possible optimization, we could define a DI intrinsic service interface that any IDisposable service could depend on, and through which it could remove itself from the list of disposables that it’s owner scope maintains, akin to how it would call GC.SuppressFinalize(this)
to remove itself from the finalizer queue as part of the Dispose implementation.
Issue Analytics
- State:
- Created 4 years ago
- Comments:33 (14 by maintainers)
Top GitHub Comments
Going through all of the individual discussions, I wanted to try to summarize all of the proposed solutions and why they aren’t favorable enough to implement.
I’ve also added some situations and solutions that I believe fit the general discussions’ recommendations. Finally, I’ve made some general guidelines from those.
Proposed Solutions
Disposed Event
Summary
Have an interface that ensures a Disposed event is exposed, implementers are expected to call the Disposed event when Dispose is called on it. Scopes would register for the event after constructing the service instance, would remove item from _disposables if the service gets disposed ad-hoc.
Problems
Owned<T> aka INotifyWhenDisposed
Summary
A type that owns the actual disposable item. Allows resolution of T’s dependencies to be done by and registered with the service provider while allowing T to be disposed manually through Owned<T>.Dispose(), which will also release T’s reference.
Problems
WeakReferences
Summary
Instead of holding normal references to the disposable items, use WeakReference instances to Dispose the instances if they haven’t already been GC’d.
Problems
Make auto-disposal a registration option
Summary
Add some way to opt-out of auto-disposal of instances during registration.
Problems
Just don’t track or dispose transients
Summary
Don’t add transients to _disposables at all or maybe add transients with a WeakReference.
Problems
Situations and Solutions
Transient, limited lifetime
Situation
You need an IDisposable instance with a transient lifetime that you either are resolving in the root scope or would like it to be disposed before the scope ends.
Solution
Use the Factory pattern to create an instance outside the parent scope. In this situation, you would generally have a Create() method that calls the final type’s constructor directly. If the final type has other dependencies, the factory can receive an IServiceProvider in its constructor and then use ActivatorUtilities.CreateInstance<T>(IServiceProvider) to instantiate the instance outside the container while using the container for its dependencies.
Shared Instance, limited lifetime
Situation
You need to share an IDisposable instance across multiple services but you need the IDisposable to have a limited lifetime.
Solution
Register the instance with a Scoped lifetime. Use IServiceProvider’s CreateScope() to start a create a new IServiceScope, then use that scope’s ServiceProvider to get your service(s). Dispose the scope when you want the lifetime to end.
General Guidelines
Triage: The original motivating scenario (Blazor Server) no longer needs this change.
Not tracking transient services for disposal may well have been a better choice but it’s significantly breaking and not really a viable option for us here. Closing as we don’t plan to take further action at this time.