I wrote an ASP.NET Core application (.NET 5 actually) and run it under Linux as a systemd service (following this Microsoft guide). Then I wanted to monitor this application with monit application. This application requires services to create a PID file so they are easily discoverable. I decided I'll write a Q&A-style question to help others achieve the same faster.
Note: the solution described here is a part of my NuGet package with open source available on github in case you prefer to achieve your goal the package way.
There are a few ways in .NET Core to run a background task. IHostedService
is one of them that contains StartAsync
method which, after a service is registered, is called on the startup. The interface also defines StopAsync
which is called on a graceful shutdown. Visit this Microsoft page for more details on IHostedService
.
Knowing that we can create our own IHostedService that will manage the PID file of our application:
public class PidFileHostedService : IHostedService
{
private readonly ILogger<PidFileHostedService> logger;
private readonly IConfiguration configuration;
private readonly IWebHostEnvironment env;
private bool isPidFileCreated = false;
private string pidFile = string.Empty;
public PidFileHostedService(ILogger<PidFileHostedService> logger,
IConfiguration configuration,
IWebHostEnvironment env)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.env = env ?? throw new ArgumentNullException(nameof(env));
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
if (env.IsDevelopment())
return;
pidFile = configuration.GetValue<string>("PidFile");
if (string.IsNullOrWhiteSpace(pidFile))
return;
await WritePidFile();
isPidFileCreated = true;
}
catch (Exception ex)
{
logger.LogError(ex, $"Unexpected error when starting {nameof(PidFileHostedService)}", null);
}
}
private async Task WritePidFile()
{
var processId = Environment.ProcessId.ToString();
await File.WriteAllTextAsync(pidFile, processId);
}
public Task StopAsync(CancellationToken cancellationToken)
{
try
{
if (isPidFileCreated)
File.Delete(pidFile);
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error when deleting PID file", null);
}
return Task.CompletedTask;
}
}
This service assumes there is the PidFile
property in the appsettings.json file of an application.
Do not forget to register the service in the Startup.cs:
services.AddHostedService<PidFileHostedService>();
As I wrote in the question I used Microsoft guide on how to make my app run as a systemd service. Now, after the changes, the service has to have necessary permissions to the directory in which it will create the PID file. Creation of this directory with necessary permission can be delegated to systemd. It is enough to add this line to the service file under [Service]
section:
RuntimeDirectory=helloapp
Thanks to this line systemd will create helloapp
subdirectory in /var/run
on every service start with necessary permissions. It is also a good idea to add the following line to the same section which will tell systemd to double check the PID file is deleted after the app is stopped:
PIDFile=/var/run/helloapp/helloapp.pid