Killing Gracefully a .Net Core Daemon Running on Linux

Killing gracefully a .NET Core daemon running on Linux

You want to be able to send a SIGTERM to the running process:

kill <PID>

And the process should handle it to shutdown correctly.

Unfortunately .NET Core is not well documented, but it is capable of handling Unix signals (in a different fashion from Mono). GitHub issue

If you use Ubuntu with Upstart, what you need is to have an init script that sends the the kill signal on a stop request: Example init script

Add a dependency to your project.json:

"System.Runtime.Loader": "4.0.0"

This will give you the AssemblyLoadContext.

Then you can handle the SIGTERM event:

AssemblyLoadContext.Default.Unloading += MethodInvokedOnSigTerm;

Note:

Using Mono, the correct way of handling it would be through the UnixSignal: Mono.Unix.Native.Signum.SIGTERM

EDIT:

As @Marc pointed out in his recent answer, this is not anymore the best way to achieve this. From .NET Core 2.0 AppDomain.CurrentDomain.ProcessExit is the supported event.

Gracefully shutdown a generic host in .NET Core 2 linux daemon

Summarizing the conversation below the initial question.

It appears that the IHostedService used in the HostBuilder is what is controlling the SIGTERM. Once the Task has been marked as completed it determines the service has gracefully shutdown. By moving the System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2"); and the code in the finally block inside the scope of the service this was able to be fixed. Modified code provided below.

public static async Task Main(string[] args)
{
Console.WriteLine("Starting");

var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<DaemonService>();
});

System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 1");
await host.RunConsoleAsync();
}
public class DaemonService : IHostedService, IDisposable
{
public Task StartAsync(CancellationToken cancellationToken)
{
System.IO.File.WriteAllText("/path-to-app/_Start.txt", "Line 1");

return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

public void Dispose()
{
try
{
System.IO.File.WriteAllText("/path-to-app/_Dispose.txt", "Line 1");
System.IO.File.WriteAllText("/path-to-app/_Stop.txt", "Line 1");
}
finally
{
System.IO.File.WriteAllText("/path-to-app/_main-finally.txt", "Line 1");
}
}
}

As this is running as a service we came to the conclusion that it actually made sense to contain the finalisation of the service itself within that scope, similar to the way ASP.NET Core applications function by providing just the service inside the Program.cs file and allowing the service itself to maintain its dependencies.

My advice would be to contain as much as you can in the service and just have the Main method initalize it.

How to write a linux daemon with .Net Core

I toyed with an idea similar to how .net core web host waits for shutdown in console applications. I was reviewing it on GitHub and was able to extract the gist of how they performed the Run

https://github.com/aspnet/Hosting/blob/15008b0b7fcb54235a9de3ab844c066aaf42ea44/src/Microsoft.AspNetCore.Hosting/WebHostExtensions.cs#L86

public static class ConsoleHost {
/// <summary>
/// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM.
/// </summary>
public static void WaitForShutdown() {
WaitForShutdownAsync().GetAwaiter().GetResult();
}


/// <summary>
/// Runs an application and block the calling thread until host shutdown.
/// </summary>
/// <param name="host">The <see cref="IWebHost"/> to run.</param>
public static void Wait() {
WaitAsync().GetAwaiter().GetResult();
}

/// <summary>
/// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered.
/// </summary>
/// <param name="host">The <see cref="IConsoleHost"/> to run.</param>
/// <param name="token">The token to trigger shutdown.</param>
public static async Task WaitAsync(CancellationToken token = default(CancellationToken)) {
//Wait for the token shutdown if it can be cancelled
if (token.CanBeCanceled) {
await WaitAsync(token, shutdownMessage: null);
return;
}
//If token cannot be cancelled, attach Ctrl+C and SIGTERN shutdown
var done = new ManualResetEventSlim(false);
using (var cts = new CancellationTokenSource()) {
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: "Application is shutting down...");
await WaitAsync(cts.Token, "Application running. Press Ctrl+C to shut down.");
done.Set();
}
}

/// <summary>
/// Returns a Task that completes when shutdown is triggered via the given token, Ctrl+C or SIGTERM.
/// </summary>
/// <param name="token">The token to trigger shutdown.</param>
public static async Task WaitForShutdownAsync(CancellationToken token = default (CancellationToken)) {
var done = new ManualResetEventSlim(false);
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) {
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: string.Empty);
await WaitForTokenShutdownAsync(cts.Token);
done.Set();
}
}

private static async Task WaitAsync(CancellationToken token, string shutdownMessage) {
if (!string.IsNullOrEmpty(shutdownMessage)) {
Console.WriteLine(shutdownMessage);
}
await WaitForTokenShutdownAsync(token);
}


private static void AttachCtrlcSigtermShutdown(CancellationTokenSource cts, ManualResetEventSlim resetEvent, string shutdownMessage) {
Action ShutDown = () => {
if (!cts.IsCancellationRequested) {
if (!string.IsNullOrWhiteSpace(shutdownMessage)) {
Console.WriteLine(shutdownMessage);
}
try {
cts.Cancel();
} catch (ObjectDisposedException) { }
}
//Wait on the given reset event
resetEvent.Wait();
};

AppDomain.CurrentDomain.ProcessExit += delegate { ShutDown(); };
Console.CancelKeyPress += (sender, eventArgs) => {
ShutDown();
//Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
}

private static async Task WaitForTokenShutdownAsync(CancellationToken token) {
var waitForStop = new TaskCompletionSource<object>();
token.Register(obj => {
var tcs = (TaskCompletionSource<object>)obj;
tcs.TrySetResult(null);
}, waitForStop);
await waitForStop.Task;
}
}

I tried adapting something like a IConsoleHost but quickly realized I was over-engineering it. Extracted the main parts into something like await ConsoleUtil.WaitForShutdownAsync(); that operated like Console.ReadLine

This then allowed the utility to be used like this

public class Program {

public static async Task Main(string[] args) {
//relevant code goes here
//...

//wait for application shutdown
await ConsoleUtil.WaitForShutdownAsync();
}
}

from there creating a systemd as in the following link should get you the rest of the way

Writing a Linux daemon in C#

.NET core BackgroundService does not shut down gracefully as daemon

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();

.net core application as a service on ubuntu

A service in Linux can just be a regular console application in .NET Core.

To have it behaving like a daemon, handling start and stop, you can have a look at this answer:

Killing gracefully a .NET Core daemon running on Linux

You can use Microsoft.AspNetCore.Owin to self host the web application.

ASP.NET Core has its own implementation of OWIN:

https://docs.asp.net/en/latest/fundamentals/owin.html



Related Topics



Leave a reply



Submit