Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly and safely dispose of singletons instances registered in the container when an ASP.NET Core app shuts down

I am looking for guidance on how to correctly and safely dispose of registered singleton instances when my ASP.NET Core 2.0 app is shutting down.

According to the following document, if I register a singleton instance (via IServiceCollection) the container will never attempt to create an instance (nor will it dispose of the instance), thus I am left to dispose of these instances myself when the app shuts down.

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.0 (2.1 has the same guidance)

I enclose some pseudo code that illustrates what I am trying to achieve.

Note I am having to maintain a reference to IServiceCollection since the IServiceProvider provided to the OnShutDown method is a simple service locator and doesn't give me the ability to execute complex queries.

When the app shuts down I want a generic way to ensure all singleton instances are disposed. I could maintain a reference to all these singleton instances directly but this doesn't scale well.

I originally used the factory method which would ensure the DI managed the lifetime of my objects, however, the execution of the factory method happened at runtime in the pipeline of handling a request, which meant that if it threw an exception the response was 500 InternalServerError and an error was logged. By creating the object directly I am striving for faster feedback so that errors on startup lead to a automatic rollback during the deployment. This doesn't seem unreasonable to me, but then at the same time I don't to misuse the DI.

Does anyone have any suggestions how I can achieve this more elegantly?

namespace MyApp
{
    public class Program
    {
        private static readonly CancellationTokenSource cts = new CancellationTokenSource();

        protected Program()
        {
        }

        public static int Main(string[] args)
        {
            Console.CancelKeyPress += OnExit;
            return RunHost(configuration).GetAwaiter().GetResult();
        }

        protected static void OnExit(object sender, ConsoleCancelEventArgs args)
        {
            cts.Cancel();
        }

        static async Task<int> RunHost()
        {
            await new WebHostBuilder()
                .UseStartup<Startup>()
                .Build()
                .RunAsync(cts.Token);
        }
    }

    public class Startup
    {
        public Startup()
        {
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // This has been massively simplified, the actual objects I construct on the commercial app I work on are
            // lot more complicated to construct and span several lines of code.
            services.AddSingleton<IDisposableSingletonInstance>(new DisposableSingletonInstance());

            // See the OnShutdown method below
            this.serviceCollection = services;
        }

        public void Configure(IApplicationBuilder app)
        {
            var applicationLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
            applicationLifetime.ApplicationStopping.Register(this.OnShutdown, app.ApplicationServices);

            app.UseAuthentication();
            app.UseMvc();
        }

        private void OnShutdown(object state)
        {
            var serviceProvider = (IServiceProvider)state;

            var disposables = this.serviceCollection
                .Where(s => s.Lifetime == ServiceLifetime.Singleton &&
                            s.ImplementationInstance != null &&
                            s.ServiceType.GetInterfaces().Contains(typeof(IDisposable)))
                .Select(s => s.ImplementationInstance as IDisposable).ToList();

            foreach (var disposable in disposables)
            {
                disposable?.Dispose();
            }
        }
    }
}
like image 497
Dungimon Avatar asked Jul 24 '18 14:07

Dungimon


People also ask

Do transient services get disposed?

Disposable transient services are captured by the container for disposal. This can turn into a memory leak if resolved from the top-level container. Enable scope validation to make sure the app doesn't have singletons that capture scoped services.

How do you dispose of resources using dependency injection?

When you make it explicit, you've got basically two options: 1. Configure the DI to return transient objects and dispose these objects yourself. 2. Configure a factory and instruct the factory to create new instances.

What is dispose in asp net?

The ASP.NET MVC framework calls Dispose when the request has completed processing. Developers typically do not have to call Dispose.

How check object is disposed or not in C#?

The object must not throw an exception if its Dispose method is called multiple times. Instance methods other than Dispose can throw an ObjectDisposedException when resources are already disposed." msdn.microsoft.com/en-us/library/… @HansPassant: If an object implements IDisposable , one can call (IDisposable.


2 Answers

It's the DI's job to dispose of any IDisposable objects it creates, whether transient, scoped or singleton. Don't register existing singletons unless you intend to clean them up afterwards.

In the question's code there's no reason to register an instance of DisposableSingletonInstance. It should be registered with :

services.AddSingleton<IDisposableSingletonInstance,DisposableSingletonInstance>();

When the IServiceCollection gets disposed, it will call Dispose() on all the disposable entities created by it. For web applications, that happens when RunAsync() ends;

The same holds for scoped services. In this case though, the instances will be disposed when the scope exits, eg when a request ends.

ASP.NET creates a scope for each request. If you want your service to be disposed when that request ends, you should register it with :

services.AddScoped<IDisposableSingletonInstance,DisposableSingletonInstance>();

Validation

For the latest edit :

By creating the object directly I am striving for faster feedback so that errors on startup lead to a automatic rollback during the deployment.

That's a different problem. Deployment errors are often caused by bad configuration values, unresponsive databases etc.

Validating Services

A very quick & dirty way to check would be to instantiate the singleton once all startup steps are complete with :

services.GetRequiredService<IDisposableSingletonInstance>();

Validating Configuration

Validating the configuration is more involved but not that tricky. One could use Data Annotation attributes on the configuration classes for simple rules and use the Validator class to validate them.

Another option is to create an IValidateable interface with a Validate method that has to be implemented by each configuration class. This makes discovery easy using reflection.

This article shows how the IValidator interface can be used in conjunction with an IStartupFilter to validate all configuration objects when an application starts for the first time

From the article :

public class SettingValidationStartupFilter : IStartupFilter  
{
    readonly IEnumerable<IValidatable> _validatableObjects;
    public SettingValidationStartupFilter(IEnumerable<IValidatable> validatableObjects)
    {
        _validatableObjects = validatableObjects;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        foreach (var validatableObject in _validatableObjects)
        {
            validatableObject.Validate();
        }

        //don't alter the configuration
        return next;
    }
}

The constructor gets all instances that implement IValidatable from the DI provider and calls Validate() on them

like image 66
Panagiotis Kanavos Avatar answered Oct 21 '22 11:10

Panagiotis Kanavos


That's not accurate. Singletons are disposed at app shutdown, though it's kind of not actually all that relevant because when the process stops, everything goes with it anyways.

The general rule of thumb is that when using DI, you should use DI all the way down, which then means you'll almost never be disposing on your own, anywhere. It's all about ownership. When you new stuff up yourself, you're also then responsible for disposing of it. However, when using DI, the container is what's newing things up, and therefore, the container and only the container should then dispose of those things.

like image 33
Chris Pratt Avatar answered Oct 21 '22 12:10

Chris Pratt