question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DI: Allow IDisposable services to get garbage collected as soon as they are disposed

See original GitHub issue

This 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:closed
  • Created 4 years ago
  • Comments:33 (14 by maintainers)

github_iconTop GitHub Comments

3reactions
brandondahlercommented, Sep 19, 2019

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

  • Couples types to DI, which is generally undesirable
  • Requires services to participate in order for it to work
  • Not backwards compatible

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

  • Becomes viral in nature or requires some recursive implementation.
  • Doesn’t really resolve the problem in an elegant manner, probably makes more problems than it solves.

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

  • GC timing is not reliable and may result in the same code sometimes calling .Dispose and other times being GC’d without disposal.
  • If only done in the root scope, that would makes it weirdly special and may lead to just as much confusion because of the non-uniform behavior.

Make auto-disposal a registration option

Summary

Add some way to opt-out of auto-disposal of instances during registration.

Problems

  • Allows for non-uniformity across libraries, some may register with and others without by default.
  • Decision is made at registration instead of at the call site.

Just don’t track or dispose transients

Summary

Don’t add transients to _disposables at all or maybe add transients with a WeakReference.

Problems

  • Breaking change, we currently call Dispose on transients.
  • Using WeakReference on transients is still a breaking change and also has the same problem as WeakReferences above.

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

  1. You probably shouldn’t register IDisposable instances in Transient scope, use the factory pattern instead.
  2. Don’t resolve Transient or Scoped IDisposable instances in the root scope unless you’re (re-)creating and disposing the IServiceProvider (which you also probably don’t want to be doing).
  3. Receiving an IDisposable dependency via DI does not infect the receiver with the need to implement IDisposable itself. The receiver of the IDisposable dependency should not call Dispose() on that dependency.
  4. Scopes should be used to control lifetimes of services. Scopes are not hierarchical and there is no special connection among scopes.
1reaction
analogrelaycommented, Apr 29, 2020

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

DI: Allow IDisposable services to get garbage collected as ...
A type that owns the actual disposable item. Allows resolution of T's dependencies to be done by and registered with the service provider...
Read more >
Will dependency injection dispose the following registration?
1 Answer. The container calls Dispose for the IDisposable types it creates. Services resolved from the container should never be disposed by ......
Read more >
Four ways to dispose IDisposables in ASP.NET Core
This post presents the options for disposing services in ASP.NET Core: manually; using the DI container; with RegisterForDispose; ...
Read more >
Implement a Dispose method
The Dispose method is primarily implemented to release unmanaged resources. When working with instance members that are IDisposable ...
Read more >
All about IDisposable
Instance of FileStream goes to garbage collection, lives there ... (DI) to get instances means you don't need to call Dispose() manually.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found