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
Serialport Not Receiving Any Data
Multiline String Literal in C#
Split a String by Another String in C#
What Does the @ Symbol Before a Variable Name Mean in C#
Comparing Object Properties in C#
Convert Datatable to Json in C#
How to Create Linq Expression Tree to Select an Anonymous Type
Get String Between Two Strings in a String
Ipc Mechanisms in C# - Usage and Best Practices
What Does "Use of Unassigned Local Variable" Mean
How to Make a Window Always Stay on Top in .Net
The Order of Elements in Dictionary
How to Compile .Net Core App for Linux on a Windows MAChine
When to Use .First and When to Use .Firstordefault With Linq