skip to Main Content

I am working on a .NET Core 3.1 background service to be installed as a daemon on an Debian AWS EC2 instance.
It is important to gracefully shut down the daemon to stop running tasks and finalize a number of tasks to be handled (sending some events, etc).
The basic implementation looks like this:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyApp.WorkerService
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).UseSystemd().Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });        
    }
}

You can see I am using the SystemdLifetime here.

The worker is as follows:

using System;
using System.Threading;
using System.Threading.Tasks;
using AdClassifier.App.Audit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog;

namespace MyApp.WorkerService
{
    public class Worker : BackgroundService
    {
        private static readonly ILogger Logger = LogManager.GetLogger(typeof(Worker).FullName);

        private readonly int _jobPollIntervalMilliseconds;

        public IServiceProvider Services { get; }

        public Worker(IServiceProvider services, IConfiguration configuration)
        {
            Services = services;
            _jobPollIntervalMilliseconds = configuration.GetValue<int>("JobPollIntervalMilliseconds");
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Logger.Info("Worker running.");
            var task = new Task(o => DoWork(stoppingToken), stoppingToken);
            task.Start();
            return task;
        }

        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            Logger.Info("Worker stopping");
            await base.StopAsync(cancellationToken);
            Logger.Info("Worker stopped");
        }

        private void DoWork(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                using (var scope = Services.CreateScope())
                {
                    // do some work
                }

                Thread.Sleep(_jobPollIntervalMilliseconds);
            }
            Logger.Info("cancellation requested!");
        }        
    }
}

The problem
As I mentioned, we are setting this up as a daemon, like this

[Unit]
Description=my worker
Requires=deploy-my-worker.service
After=multi-user.target deploy-my-worker.service
ConditionFileIsExecutable=/home/my-worker/current/myworker
[Service]
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=true
Environment=ASPNETCORE_URLS=http://*:5000
Environment=DOTNET_ENVIRONMENT=Staging
Environment=ASPNETCORE_ENVIRONMENT=Staging
WorkingDirectory=/home/my-worker/current
ExecStart=/home/my-worker/current/myworker
SyslogIdentifier=my-worker
Restart=always
RestartSec=10
KillSignal=SIGTERM
User=usr
Group=usrgroup
[Install]
WantedBy=multi-user.target

The problem is that the worker will not stop gracefully. I am checking logs for the following log entries, but they do not appear:
Worker stopping, cancellation requested!, Worker stopped
Note that the application does shut down. What we have tried in order to shut down the service are the following:

  • shut down the server
  • systemctl stop my-worker.service
  • kill
  • kill -SIGTERM
  • kill -SIGINT

What Works

If I start the worker like this: usr@ip-10-168-19-126:~/my-worker/current$ ./myworker and then press Ctrl-C (which should be a SIGINT), the application stops, and in my logs I can see the correct messages:

2020-05-21 16:16:57.9676|INFO|MyApp.WorkerService.Worker|Worker stopping 
2020-05-21 16:16:57.9937|INFO|MyApp.WorkerService.Worker|cancellation requested! 
2020-05-21 16:16:57.9937|INFO|MyApp.WorkerService.Worker|Worker stopped 
2020-05-21 16:16:57.9980 Info AppDomain Shutting down. Logger closing...

Any ideas how I can get the daemon to work as expected?

NOTE:
I have good reason to believe that the problem lies somewhere in the daemon setup, or UseSystemd().
I replaced UseSystemd() with UseWindowsService() and installed it as a Windows service on a windows machine. Then went forward with starting and stopping the service via the Services panel, and saw shutdown logging as expected.
So, I am tempted to assume that there is no problem in the implementation, but rather somewhere in the setup.

4

Answers


  1. Chosen as BEST ANSWER

    It seems that the problem lay with NLog's shutdown. This was fixed by doing the following: LogManager.AutoShutdown = false; and in Worker::StopAsync adding LogManager.Shutdown();


  2. It sounds like a threading issue … where the application exits before completion of the task(s).

    You might give this a try:

            protected override Task ExecuteAsync(CancellationToken stoppingToken)
            {
                System.Threading.Thread.CurrentThread.IsBackground = false;
    
                Logger.Info("Worker running.");
                var task = Task.Run(() => DoWork(stoppingToken), stoppingToken);
                task.Wait();
                return task;
            }
    

    The key is to make your thread that is executing the task, wait for the task and NOT be in the background. I believe the application will exit after completion of any calls to ExecuteAsync.

    Login or Signup to reply.
  3. I’m facing the same problem, and it seems to be because systemctl stop is not sending the SIGTERM signal, so to make the service work as expected, I configured the service file with the following parameters:

    [Service]
    Type=simple
    ExecStop=/bin/kill -s TERM $ MAINPID
    

    Now, StopAsync and Dispose are called when I run a systemctl stop MyService

    Login or Signup to reply.
  4. Following service definition did solve the problem in my case. I paste the whole service definition, so don’t get confused.

    These two attributes made the ASP.NET Core service shutdown gracefully:

    • KillSignal=SIGTERM
    • ExecStop=/bin/kill ${MAINPID}

    Let me know if it solves your problem too.

    [Unit]
    Description=WeatherDisplay.Api Service
    After=network-online.target firewalld.service
    Wants=network-online.target
    
    [Service]
    Type=notify
    WorkingDirectory=/home/pi/WeatherDisplay.Api
    ExecStart=/home/pi/WeatherDisplay.Api/WeatherDisplay.Api
    ExecStop=/bin/kill ${MAINPID}
    KillSignal=SIGTERM
    SyslogIdentifier=WeatherDisplay.Api
    
    User=pi
    Group=pi
    
    Restart=always
    RestartSec=5
    
    Environment=ASPNETCORE_ENVIRONMENT=Production
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
    Environment=DOTNET_ROOT=/home/pi/dotnet
    
    [Install]
    WantedBy=multi-user.target
    

    You can find the full project here:
    https://github.com/thomasgalliker/PiWeatherStation

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search