Enrico Massone
Enrico Massone

Reputation: 7338

What happens if the implementation of IHostedService.StopAsync method does not respond to cancellation requests?

We are implementing a .NET core 3.1 worker service and we need to register several hosted service to implement the required business.

This documentation explains that the StopAsync method of the IHostedService interface should be able to listen to cancellation requests issued after a default 5 seconds timeout.

Based on my understanding, the StopAsync method is called by the host during the shutdown process of the host itself.

In one of our hosted services we need to call an async method during the hosted service stop process; unfortunately this method does not support cancellation, so there is not a simple way for us to implement the StopAsync method in the proper way.

This is our scenario:

public sealed class MyHostedService : IHostedService 
{
     public async Task StopAsync(CancellationToken token)
     {
          _logger.LogInformation("Stopping the service....");

           await DoSomethingAsync(); // this method does not support cancellation, so I can't pass the cancellation token here

          _logger.LogInformation("Service was stopped");
     }
}

What's going to happen at the host level in this scenario ?

Is this implementation "dangerous" in any sort ?

Is this going to block the host shutdown process in case of a long time needed to complete the execution of StopAsync ?

The simplest solution to this issue is implementing the class MyHostedService by deriving from the BackgroundService base class, but I would like to fully understand the possible implications of a long running StopAsync implementation at the host level.

Upvotes: 2

Views: 1846

Answers (2)

pinkfloydx33
pinkfloydx33

Reputation: 12739

Hosted Services are stopped in the reverse order from which they were started (which is also the order you add them to the ServiceCollection). You can extend the shutdown timeout by specifying HostOptions.ShutdownTimeout which otherwise defaults to five seconds:

services.Configure<HostOptions>(s => s.ShutdownTimeout = TimeSpan.FromMinutes(5));

When the Host is stopping its hosted services it does so in a loop, running over the services in reverse order. Once StopAsync is called for a particular service, the host will wait for StopAsync to return. So if that one service doesn't respect cancellation, the host will take a while to shutdown.

The host also calls ThrowIfCancellationRequested() in that loop prior to calling StopAsync on each service. What this means is that if the timeout elapses while the first service is stopping, it will finish completing* but before the next service's StopAsync is called an exception will be thrown. This will cause the shutdown process to abort, and for the process to exit ungracefully.

This abort can also happen after all services have been stopped if the timeout elapses in the very next instant (there is another ThrowIfCancellationRequested immediately after the loop). Anything bound to the Host/Application lifetimes will be skipped.

Moral of the story is to either ensure your services can stop quickly, don't require your services to need graceful termination, or increase the timeout. You can also make sure you add the services to the collection in such a way that the more important or quicker ones get stopped first (by adding them last) and your long running service gets stopped last (by adding it first).

* Assuming the service doesn't respect cancellation

Upvotes: 4

Guru Stron
Guru Stron

Reputation: 141565

Based on generic and web hosts shutdown timeout docs the host will just stop corresponding services ungracefully:

If the timeout period expires before all of the hosted services stop, any remaining active services are stopped when the app shuts down. The services stop even if they haven't finished processing. If services require additional time to stop, increase the timeout.

So long running StopAsync should not prevent the host from proceeding with shutdown. The only bad thing that can happen is that your DoSomethingAsync will be interrupted in some invalid state, if that is possible.

Upvotes: 0

Related Questions