22

I wanted to make a safe exit for my console application that will be running on linux using mono but I can't find a solution to detect wether a signal was sent to it or the user pressed ctrl+c.

On windows there is the kernel function SetConsoleCtrlHandler which does the job but that doesnt work on mono.

How do I get a closing event on my console application to safe exit it ?

Prix
  • 19,417
  • 15
  • 73
  • 132

2 Answers2

13

As an example of providing a unix and windows implementation see below. Note that you can still include the Mono.Posix dll when using Visual Studio.

I have added the SIGTERM signal as well because this is fired by systemd in unix when stopping / restarting your app as a service.

Interface to expose an exit event

public interface IExitSignal
{
    event EventHandler Exit;
}

Unix implementation

public class UnixExitSignal : IExitSignal
{
    public event EventHandler Exit;

    UnixSignal[] signals = new UnixSignal[]{
        new UnixSignal(Mono.Unix.Native.Signum.SIGTERM), 
        new UnixSignal(Mono.Unix.Native.Signum.SIGINT),
        new UnixSignal(Mono.Unix.Native.Signum.SIGUSR1)
    };

    public UnixExitSignal()
    {
        Task.Factory.StartNew(() => 
        {
            // blocking call to wait for any kill signal
            int index = UnixSignal.WaitAny(signals, -1);

            if (Exit != null)
            {
                Exit(null, EventArgs.Empty);
            }

        });
    }

}

Windows implementation

public class WinExitSignal : IExitSignal
{
    public event EventHandler Exit;

    [DllImport("Kernel32")]
    public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

    // A delegate type to be used as the handler routine
    // for SetConsoleCtrlHandler.
    public delegate bool HandlerRoutine(CtrlTypes CtrlType);

    // An enumerated type for the control messages
    // sent to the handler routine.
    public enum CtrlTypes
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    /// <summary>
    /// Need this as a member variable to avoid it being garbage collected.
    /// </summary>
    private HandlerRoutine m_hr;

    public WinExitSignal()
    {
        m_hr = new HandlerRoutine(ConsoleCtrlCheck);

        SetConsoleCtrlHandler(m_hr, true);

    }

    /// <summary>
    /// Handle the ctrl types
    /// </summary>
    /// <param name="ctrlType"></param>
    /// <returns></returns>
    private bool ConsoleCtrlCheck(CtrlTypes ctrlType)
    {
        switch (ctrlType)
        {
            case CtrlTypes.CTRL_C_EVENT:
            case CtrlTypes.CTRL_BREAK_EVENT:
            case CtrlTypes.CTRL_CLOSE_EVENT:
            case CtrlTypes.CTRL_LOGOFF_EVENT:
            case CtrlTypes.CTRL_SHUTDOWN_EVENT:
                if (Exit != null)
                {
                    Exit(this, EventArgs.Empty);
                }
                break;
            default:
                break;
        }

        return true;
    }


}
James
  • 735
  • 1
  • 7
  • 13
  • Note that I had problems with Task.Factory.StartNew that seemed to be fixed with a dedicated background thread (while using mono in a docker container); has anyone else encountered this? – J Trana Apr 28 '16 at 20:16
  • Run time of handler which is set by SetConsoleCtrlHandler is limited to 5s . – irvnriir Aug 30 '23 at 11:39
13

You need to use Mono.UnixSignal, there's a good sample posted by Jonathan Pryor : http://www.jprl.com/Blog/archive/development/mono/2008/Feb-08.html

There's also a shorter example on Mono page: FAQ / Technical / Operating System Questions / Signal Handling:

// Catch SIGINT and SIGUSR1
UnixSignal[] signals = new UnixSignal [] {
    new UnixSignal (Mono.Unix.Native.Signum.SIGINT),
    new UnixSignal (Mono.Unix.Native.Signum.SIGUSR1),
};

Thread signal_thread = new Thread (delegate () {
    while (true) {
        // Wait for a signal to be delivered
        int index = UnixSignal.WaitAny (signals, -1);

        Mono.Unix.Native.Signum signal = signals [index].Signum;

        // Notify the main thread that a signal was received,
        // you can use things like:
        //    Application.Invoke () for Gtk#
        //    Control.Invoke on Windows.Forms
        //    Write to a pipe created with UnixPipes for server apps.
        //    Use an AutoResetEvent

        // For example, this works with Gtk#    
        Application.Invoke (delegate () { ReceivedSignal (signal); });
    }});
skolima
  • 31,963
  • 27
  • 115
  • 151
  • and how the invoke would look like for a console app ? You are missing a } from the while – Prix Jul 01 '11 at 10:55
  • I can't find the Mono.Posix to reference on my project could you give me some extra directions on this matter... both links are very incomplete ;/ – Prix Jul 01 '11 at 11:13
  • 1
    @Prix: Mono.Posix.dll is installed by the standard Mono 2.10.2 installer, what distribution are you using? Also, for a console application, you can skip the `Application.Invoke` and just call the cleanup logic from the signal thread. – skolima Jul 01 '11 at 12:40
  • I am developing the application on VS2010 hence i need it working on windows for testing before putting it on the linux server. I know the dll is on the linux server but it doesnt work if i get that dll and refernce it so i am clueless on how to proceed – Prix Jul 01 '11 at 13:03
  • 2
    You won't be able to test this code on Windows, as it relies on OS-specific functionality. What you have to do is to implement both behaviours (using `SetConsoleCtrlHandler` for Windows and `UnixSignal` for Linux) and then choose one or the other _during runtime_ based on `Environment.OSVersion.Platform` - use `UnixSignal` when running on `Unix` or `MacOSX`. – skolima Jul 01 '11 at 15:36