-
Notifications
You must be signed in to change notification settings - Fork 10.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
Solution for accumulation of Disposable transient services #5496
Comments
It's not a bug, as in general we don't know whether you want the service instance's lifetime to be scoped to that component. You might want to keep using that service instance over time. In general your component needs to make its own decisions about what to dispose in its own Closing as by design, but please let me know if you happen to know that the conventions are definitely different in some specific other case with ASP.NET. |
The current behavior is clear for singletons. I am not sure if it makes sense for transients (scoped is currently not used, is it?). For transients, a new instance is created every time the service is requested. Therefore, it would be quite save to dispose the service once the component gets disposed. One design problem with the current behavior is that the component receiving the service does not know whether it is transient or singleton. How should the component decide whether it should dispose the service instance? The ASP.NET Core docs says: The container will call |
Correct
Isn't that the same for, say ASP.NET MVC controllers receiving services as constructor args?
For singletons, it doesn't make sense ever to dispose the instance (except if the What I'm not currently sure about (because I haven't yet investigated) is how that's even possible under the Clearly this requires a bit more research so I'll leave this issue open with a modified title. |
I did not check the ASP.NET Core source code but my understanding is that they use scopes for each request. Once the request is done, the scope is disposed (including all created services). Blazor would have to create a scope for each component. The router would have to dispose the scope similarly to disposing the component. I cannot guarantee that this is correct. This is just my understanding of how it is done in ASP.NET Core. |
We're concerned about the potential for memory leaks here with transients. Circuit lifetime is arbitrarily long. We need to think about this. |
SummaryThe solutions to this will require a new DI feature or will required going outside the DI system entirely. The problem is that the DI system will track transient values. This means that anything you resolve as a transient is kept in memory until the DI scope is disposed. There are a few approaches to this that we've outlined so far: Self-serve unregistrationThis is the proposal from DI: dotnet/extensions#1785 - in this model each service that wants to support early disposal has to be aware of it. public class SelfDisposer : IDisposable
{
public SelfDisposer(IServiceCollectionNotifyDisposed notify)
{
}
void IDisposable.Dispose() => _notify.NotifyDisposed(this);
} HandlesThis model allows requestors to resolve a
You can think of this as a non-owning handle - like public sealed class Handle<T> : IDisposable
{
public Handle(T service, bool ownsLifetime)
{
}
public void Dispose()
{
if (_ownsLifetime && service is IDisposable disposable)
{
disposable.Dispose();
}
}
} Owing handlersThis model allows requestors to resolve an The reason why public sealed class OwningHandle<T> : IDisposable
{
public Handle(T service)
{
}
public void Dispose()
{
if (service is IDisposable disposable)
{
disposable.Dispose();
}
}
} |
@rynowak I am curious how the two handle-based approaches would work when an IDisposable service depends on other IDisposable service(es) (or a tree of them). At first glance, it seems to me that in order for the memory to be released, each IDisposable service would need to be changed to start only depending on other handles, and never on other IDisposable services directly. I.e. isn’t it kind of viral? Also, isn’t it a bit strange that the the handles themselves are IDisposable but need to break the rules of how IDisposable services work in order to release any memory? I.e. to remove their owned service (and possible themselves) from the container’s disposable list, wouldn’t they need the feature you describe as self-serve unregistration? In summary, assuming I am not missing anything:
|
Is there any reason why Blazor couldn't use an
So if blazor used a scope and disposed of it appropriately then problem solved? P.s a similar pattern can be used successfully in other UI stacks where dependencies are injected. For example in windows forms or wpf, or Xamarin forms apps, when navigating to a new UI you might have something conceptually like this: E.g:
|
So for now. do i have to dispose of every transient IDisposable Service in the dispose function of the component? I believe this should be a high priority issue. Thank you for your hard work |
Fixes: #5496 Fixes: #10448 This change adds a *utility* base class that encourages you to do the right thing when you need to interact with a disposable scoped or transient service. This solution ties the lifetime of a DI scope and a service to a component instance. Note that this is not recursive - we expect users to pass services like this around (or as cascading values) if the design dictates it.
Fixes: #5496 Fixes: #10448 This change adds a *utility* base class that encourages you to do the right thing when you need to interact with a disposable scoped or transient service. This solution ties the lifetime of a DI scope and a service to a component instance. Note that this is not recursive - we expect users to pass services like this around (or as cascading values) if the design dictates it.
Fixes: #5496 Fixes: #10448 This change adds a *utility* base class that encourages you to do the right thing when you need to interact with a disposable scoped or transient service. This solution ties the lifetime of a DI scope and a service to a component instance. Note that this is not recursive - we expect users to pass services like this around (or as cascading values) if the design dictates it.
Added a utility component for this. Will updated docs as part of the release. |
Given a service:
It is injected as a transient:
If this service is injected into a component, it is never disposed even when navigating away from the component:
Is this a bug or is there a functionality available to dispose injected services implementing
IDisposable
?The text was updated successfully, but these errors were encountered: