Application not entering Posix Signal handler in release mode
Asked Answered
D

1

5

I am trying to gracefully shutdown my Console application on a linux arm device. The signal that is sent is SIGTERM. I implemented a Posix Sgnal Handler with the new PosixSignalRegistration.Create() method. This works perfectly when compiled in Debug mode. However, in Release mode (with optimization turned on), the handler is never entered.

Here is a MRE:

using Microsoft.Extensions.Hosting;
using System.Runtime.InteropServices;

ManualResetEvent resetEvent = new(false);
CancellationTokenSource cts = new();

// Handles CTRL + C in both operating systems
Console.CancelKeyPress += (sender, eventArgs) =>
{
    Console.WriteLine("Received CTRL+C");
    resetEvent.Set();
    eventArgs.Cancel = true;
    cts.Cancel();
};

// Handles graceful kill (Sigterm) in linux (e.g. systemctl stop)
PosixSignalRegistration.Create(PosixSignal.SIGTERM, (context) =>
{
    Console.WriteLine("Received SIGTERM");
    resetEvent.Set();
    cts.Cancel();
});

IHost? host = null;

try
{
    var hostBuilder = Host.CreateDefaultBuilder(args);
    host = hostBuilder.Build();

    host.Start();

    resetEvent.Reset();
    resetEvent.WaitOne();
}
catch
{
    Console.WriteLine("Error");
}
finally
{
    Console.WriteLine("finally");
    await (host?.StopAsync(TimeSpan.FromSeconds(5)) ?? Task.CompletedTask);
    Console.WriteLine("Application shut down");
}

In Debug Mode, when calling pkill my-application -15, the Log looks as follows, and the application shuts down:

Received SIGTERM
finally
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
Application shut down

In Release mode, the only thing that happens is the following log message:

info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...

However the application keeps on running, and can only be shut down gracefully by Ctrl + C or pkill my-application -2.

What am I missing?

Doublet answered 16/11, 2022 at 11:8 Comment(0)
A
8

Note that PosixSignalRegistration.Create does not return void, it returns an instance of PosixSignalRegistration. This object represents registration of your handler, it implements IDisposable and calling Dispose on it will unregister the handler. It also has finalizer, which does the same as dispose - unregisters handler.

Now note that you ignore the return value of PosixSignalRegistration.Create. Now there are no references to that registration mentioned above, and garbage collector is free to collect it. Since it has a finalizer - it will be called and your handler will be unregistered when GC collects an instance.

That's what happens in your case. In debug mode GC will not collect it for various reasons (such as ease of debugging). In release mode however it might be collected at any time, even immediately after PosixSignalRegistration.Create call.

You might add GC.KeepAlive(yourRegistration) call at the end of the method, but in this case it would not be the best idea, because registration implements IDisposable and so you should respect that:

using (PosixSignalRegistration.Create(
    PosixSignal.SIGTERM,
    (context) =>
    {
        Console.WriteLine("Received SIGTERM");
        resetEvent.Set();
        cts.Cancel();
    }))
{
    IHost? host = null;

    try {
        var hostBuilder = Host.CreateDefaultBuilder(args);
        host = hostBuilder.Build();

        host.Start();

        resetEvent.Reset();
        resetEvent.WaitOne();
    }
    catch {
        Console.WriteLine("Error");
    }
    finally {
        Console.WriteLine("finally");
        await (host?.StopAsync(TimeSpan.FromSeconds(5)) ?? Task.CompletedTask);
        Console.WriteLine("Application shut down");
    }
}
Ardyth answered 16/11, 2022 at 12:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.