Home > Blockchain >  BackgroundService Graceful Shutdown - Complete work and write to DB
BackgroundService Graceful Shutdown - Complete work and write to DB

Time:11-22

I have a Background Worker implementing the BackgroundService (provided by MS).

See this simple implementation:

public class MyService : BackgroundService {

    private readonly MyDbContext _context;

    public MyService(MyDbContext context) {
        //...
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try {
            while (true)
            {
                stoppingToken.ThrowIfCancellationRequested();
                // Do some work
            }
        } catch(OperationCancelledException) {
            _context.Add(new MyLogMessage(){ Error = "MyService cancelled!" });
            _context.SaveChanges();
        }
        // ...
    }
}

When the graceful shutdown (in console: CTRL C) is requested the catch block is triggered, and also the SaveChanges() seems to be executed. But, sometimes the error is stored into the database and the most of the time it is not. Also the EntityFramework is printing an insert statement on the console, but the log is not in the db. I assume that the shutdown is happening faster then writting the data to the DB? Can anyone give me a hint how to handle this situation and store the error into the database?

CodePudding user response:

It seems like the stoppingToken isn't cancelled as expected when the application shuts down. I managed to get around this using IHostApplicationLifetime and a new field where I can store if a shutdown is in progress.

public class TestService : BackgroundService {
    private readonly IHostApplicationLifetime _lifetime;
    private readonly ILogger<TestService> _logger;

    private bool _shutownRequested;

    public TestService(IHostApplicationLifetime lifetime, ILogger<TestService> logger) {
        _lifetime = lifetime;
        _logger = logger;
    }

    public override Task StartAsync(CancellationToken cancellationToken) {
        _lifetime.ApplicationStopping.Register(OnShutdown);
        return Task.CompletedTask;
    }

    private void OnShutdown() {
        _shutdownRequested = true;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
        try {
            while(true) {
                stoppingToken.ThrowIfCancellationRequested();
                if(_shutdownRequested) {
                    throw new OperationCanceledException();
                }

                await Task.Delay(100, CancellationToken.None);
            }
        } catch(OperationCanceledException) {
            _logger.LogWarning("TestService canceled");
        }
    }
}

Now it might be better to now throw a new exception there, but as an example it will do.

CodePudding user response:

The reason why the log entry doesn't appear in the database is that the host shutdown period is lower than what it takes to process a task in a while loop and send a log to the database. The default timeout is 5 seconds.

What you could do, is to increase the timeout to a larger value, for example a minute a two:

services.Configure<HostOptions>(
    opts => opts.ShutdownTimeout = TimeSpan.FromMinutes(2));

Make sure to let enough time for a service to finish the iteration inside a while loop and log the message.

Please check Extending the shutdown timeout setting to ensure graceful IHostedService shutdown for more details.

  • Related