dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.12k stars 4.7k forks source link

Should .NET Generic Host call IHostApplicationLifetime.StopApplication for unhandled domain exceptions? #104428

Open amoorcroft-nortech opened 3 months ago

amoorcroft-nortech commented 3 months ago

Currently using a single IHostedService with .NET Generic host, which is ran as a Windows Service.

When an unhandled exception is thrown from a worker thread (e.g. from a System.Threading.Timer callback), the application is terminated with a non-zero exit code, resulting in the SCM restarting the service (due to configured service's recovery options).

It's possible to hook into the AppDomain.CurrentDomain.UnhandledException event, which could be used to save state / perform additional logging before the application terminates. The question is, should this event callback be invoking IHostApplicationLifetime.StopApplication? The advantage is that any unmanaged resources in the service container could be disposed of, rather than just exiting.

Looking at the docs, when an unhandled exception occurs there doesn't seem to be anyway to prevent the application from crashing after it invokes the event callback. Perhaps if IHostApplicationLifetime.StopApplication was called, we could end up in a worse state, with some services being disposed before the application crashes (as host could take a while to shutdown cleanly)?

When I tested invoking StopApplication in the event callback, I noticed the application would no longer be restarted by SCM (even when setting Environment.ExitCode to 1). However, it would log "Hosting stopped", implying the host cleanup was being completed.

Here's some sample code to demonstrate what I'm talking about:

AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
    var e = args.ExceptionObject as Exception;
    try
    {
        _logger.Error(e, "Unhandled exception, attempting to shutdown cleanly....");
    }
    catch { }
    Environment.ExitCode = 1; // TBD: this doesn't work, SCM doesn't restart service
    // the application will try to terminate anyway, but try to cleanly shutdown
    _lifetime.StopApplication(); // removing this line results in SCM restarting service.
};
new Timer((obj) => throw new Exception("Unhandled exception test"), null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));

Most relevant doc I could find on subject https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?tabs=appbuilder#host-shutdown and https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-appdomain-unhandledexception

Using .NET Version 8.0.

dotnet-policy-service[bot] commented 3 months ago

Tagging subscribers to this area: @dotnet/area-extensions-hosting See info in area-owners.md if you want to be subscribed.