Search code examples
c#linuxsignalspinvoke

P/Invoke: Fatal error. Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code


I am trying to catch kill signal in background task on Linux system (Ubuntu) using sigaction from libstd. Here is my Program class and DllImport declarations:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Signal
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var pid = LibStd.GetPid();
            Console.WriteLine($"PID: {pid}");

            var sa = new Sigaction()
            {
                Handler = SignalHandler
            };

            LibStd.SigEmptySet(sa.Mask);
            var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sa));
            Marshal.StructureToPtr(sa, ptr, true);

            _ = Task.Run(async() =>
            {
                while (true)
                {
                    // Check SIGUSR1
                    if (LibStd.Sigaction(10, ptr, IntPtr.Zero) == -1)
                    {
                        // error
                    }

                    await Task.Delay(1);
                }
            });

            while (true)
            {
                // Do something
            }
        }

        static LibStd.SignalHandler SignalHandler = new LibStd.SignalHandler(Handler);

        static void Handler(int sig)
        {
            Console.WriteLine($"RECEIVED SIGNAL {sig}");
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct Sigaction
    {
        public LibStd.SignalHandler Handler;
        public IntPtr Mask;
        public int Flags;
    }

    internal static class LibStd
    {
        private const string LibName = "c";

        [DllImport(LibName, EntryPoint = "getpid")]
        internal static extern int GetPid();

        [DllImport(LibName, EntryPoint = "sigaction", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int Sigaction(int sig, IntPtr act, IntPtr oldact);

        [DllImport(LibName, EntryPoint = "sigemptyset", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int SigEmptySet(IntPtr mask);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        internal delegate void SignalHandler(int t);
    }
}

I need to check for signals in background because I want to put this code in desktop GUI app in future so signal handling shouldn't block UI thread. However, when I run this program, then call kill -SIGUSR1 <pid> it outputs to the console following error: Fatal error. Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code. If I put signal handling into main while (true) { } loop it works as expected.
It could be great if someone could help me to fix it. Thank you


Solution

  • For Ubuntu 18 I found that the structure needed was

    [StructLayout(LayoutKind.Sequential)]
    internal struct Sigaction
    {
        public LibStd.SignalHandler Handler;
        public IntPtr Mask;
        public int Flags;
        public LibStd.SignalHandler sa_restorer;
    }
    

    consider not starting a thread for the signal processing

    static private void ListenToSignal()
    {
        var sa = new Sigaction()
        {
            Handler = SignalHandler
        };
    
        LibStd.SigEmptySet(sa.Mask);
        var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sa));
        Marshal.StructureToPtr(sa, ptr, true);
    
        if (LibStd.Sigaction(10, ptr, IntPtr.Zero) == -1)
        {
            Console.WriteLine("Sigaction -1");
        }
        Console.WriteLine("Sigaction done");        
    }
    

    This sets up the processing on the main thread. When a signal arrives it get processed on it too. There is no need to keep polling the Sigaction