Search code examples
c#c++linux.net-coresignals

Custom posix signal in C# on a linux platform does not work


Question

I am trying to send a signal in linux to a thread in C# in dotnet core in Linux. My test code works with SIGARAM but does not work with a custom signal mapping to SIGRTMIN#. sigwait on a thread never returns when a main thread raises a custom signal.

C++ version works well. C++ and C# version are pretty much the same. Can anyone figure out why it does not work?

My ultimate goal is to catch a signal from a device driver.

C++ version

//https://www.ibm.com/docs/en/i/7.4?topic=ssw_ibm_i_74/apis/sigwaiti.htm
//g++ -Wall -O2 example.cpp -o example -pthread

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <thread>

void catcher( int sig ) {
    printf( "Signal catcher called for signal %d\n", sig );
}

void timestamp( const char *str ) {
    time_t t;

    time( &t );
    printf( "The time %s is %s\n", str, ctime(&t) );
}

void on_sigusr1(int sig)
{
  // Note: Normally, it's not safe to call almost all library functions in a
  // signal handler, since the signal may have been received in a middle of a
  // call to that function.
  printf("SIGUSR1 received!\n");
}

#define SIG_SYSTEM (SIGRTMIN+7)

void run()
{
    struct sigaction sigact;
    sigset_t waitset;
    siginfo_t info;

    sigemptyset( &sigact.sa_mask );
    sigact.sa_flags = 0;
    sigact.sa_handler = catcher;
    sigaction( SIG_SYSTEM, &sigact, NULL );

    sigemptyset( &waitset );
    sigaddset( &waitset, SIG_SYSTEM );

    sigprocmask( SIG_BLOCK, &waitset, NULL );

    timestamp( "before sigwaitinfo()" );

//    int result = sigwaitinfo( &waitset, &info );
    int sig;
    int result = sigwait( &waitset, &sig );

    if( sig == SIG_SYSTEM )
        printf( "sigwaitinfo() returned for signal %d\n",
                 info.si_signo );
    else {
        printf( "sigwait() sig %d\n", sig );
        printf( "sigwait() returned code %d\n", result );
        printf( "sigwait() returned error number %d\n", errno );
        perror( "sigwait() function failed\n" );
    }

    timestamp( "after sigwaitinfo()" );
}

int main( int argc, char *argv[] ) {

    int result = 0;

    signal(SIG_SYSTEM, &on_sigusr1);

    sigset_t waitset;
    sigemptyset( &waitset );
    sigaddset( &waitset, SIG_SYSTEM );

    sigprocmask( SIG_BLOCK, &waitset, NULL );

    std::thread tx(run);

    sleep(5);
    //alarm( 5 );
    //result = raise(SIG_SYSTEM);
    result = kill(getpid(), SIG_SYSTEM);
    printf( "kill(%d) %d\n", SIG_SYSTEM, result);

    tx.join();

    return( result );
}

C# version

However a test program in C# does not work.

using System;

using static Tmds.Linux.LibC;
using Tmds.Linux;
using System.Runtime.InteropServices;

delegate void SignalCallback(int sig);

namespace example
{
    class Program
    {
        static void catcher(int sig)
        {
            Console.WriteLine($"Signal catcher called for signal {sig}");
        }

        static void timestamp(string str)
        {
            Console.WriteLine($"The time {str} is {DateTime.Now}");
        }

        static System.Threading.Thread Thread;

        static int SIG_SYSTEM = SIGRTMIN + 7;

        static unsafe void Run()
        {
            int result = 0;

            sigaction sigact = new sigaction();
            sigset_t waitset = new sigset_t();
            siginfo_t info = new siginfo_t();

            sigset_t* ptr = &sigact.sa_mask;
            sigemptyset(&sigact.sa_mask);
            sigact.sa_flags = 0;

            SignalCallback callback = new SignalCallback(catcher);
            sigact.sa_handler = (void*)Marshal.GetFunctionPointerForDelegate(callback);

            sigaction(SIG_SYSTEM, &sigact, null);

            sigemptyset(&waitset);
            sigaddset(&waitset, SIG_SYSTEM);

            sigprocmask(SIG_BLOCK, &waitset, null);

            timestamp("before sigwaitinfo()");

            //result = sigwaitinfo(&waitset, &info);
            int sig;
            result = sigwait(&waitset, &sig);

            // after it's last usage by unmanaged code
            GC.KeepAlive(callback);

            if (sig == SIG_SYSTEM)
                Console.WriteLine($"sigwaitinfo() returned for signal {info.si_signo}");
            else
            {
                Console.WriteLine($"sigwait() sig {sig}");
                Console.WriteLine($"sigwait() returned code {result}");
                Console.WriteLine($"sigwait() returned error number {errno}");
                Console.WriteLine("sigwait() function failed");
            }

            timestamp("after sigwaitinfo()");
        }

        static unsafe void Main(string[] args)
        {
            sigset_t waitset = new sigset_t();
            sigemptyset(&waitset);
            sigaddset(&waitset, SIG_SYSTEM);

            sigprocmask(SIG_BLOCK, &waitset, null);

            Thread = new System.Threading.Thread(Run);
            Thread.Start();

            //alarm(10);
            sleep(5);
            int result = kill(getpid(), SIG_SYSTEM);
            Console.WriteLine($"kill({SIG_SYSTEM}) {result}");

            Thread.Join();
        }
    }
}

Environment

  • VisualStudio Professional 2019 16.9.4
  • dotnet core SDK 3.1
  • Ubuntu on WSL
  • Tmds.Linux 0.5.0

Solution

  • I was able to get a signal writing a small .so file.

    static void (*_callback)(int, int);
    
    void catcher(int sig, siginfo_t* info, void* context) {
        int* sival_int = &info->si_int;
        _callback(sig, sival_int[1]);
    }
    
    extern "C" void setaction(int sig, void *callback)
    {
        _callback = (void(*)(int, int))callback;
    
        struct sigaction sigact;
        sigemptyset(&sigact.sa_mask);
    
        sigact.sa_flags = SA_SIGINFO;
        sigact.sa_sigaction = catcher;
        sigaction(sig, &sigact, NULL);
    }
    
    delegate void SignalCallback(int sig, int info);
    
    static SignalCallback cb = Callback;
    
    [DllImport("signalhandle.so")]
    static extern void setaction(int sig, SignalCallback callback);
    
    //Call this on the main thread.
    public static void Start()
    {
        setaction(SIG_SYSTEM, cb);
    }
    
    private static void Callback(int sig, int info)
    {
    }