Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unix pthreads and signals: per thread signal handlers

I'm having trouble getting threads to catch the correct signals.

For example,

I first start a main thread (tid 1).

Then, it sets a signal handler for SIGUSR1 to function1(), using signal(2).

The main thread creates a new thread, with tid 2.

In thread 2, I register a signal handler for SIGUSR1 to function2() using signal(2).

Thread 1 then creates a thread 3 (tid 3).

From thread 3, I use pthread_kill(1, SIGUSR1) to send a signal to thread 1.

However, function2() gets called, not function1().

Is this behavior intended, or is there something I need to change to get these signal handlers to work?

Edit: I've done a bit of debugging and it turns out that the signal IS getting sent to thread 1, however function2() is getting called from thread 1 for some reason. Is there a workaround for this?

like image 285
tomKPZ Avatar asked Jul 08 '14 23:07

tomKPZ


People also ask

Are signal handlers per thread?

signal is a per process call, not a per thread one, if you call it it sets the handler for all threads in the process. Signals and threads is a complex topic. You might be better off asking about what you really what to do. Using signal in a multithreaded program is essentially undefined behaviour.

What are signals and signal handlers in Linux?

Signal Handlers. A signal handler is special function (defined in the software program code and registered with the kernel) that gets executed when a particular signal arrives. This causes the interruption of current executing process and all the current registers are also saved.

How are signals handled in UNIX?

A routine called by the UNIX system to process a signal is termed a signal handler. A software interrupt on an OpenVMS system is referred to as a signal, condition, or exception. A routine called by the OpenVMS system to process software interrupts is termed a signal handler, condition handler, or exception handler.

Are signals shared between threads?

Signal management in multithreaded processes is shared by the process and thread levels, and consists of the following: Per-process signal handlers. Per-thread signal masks. Single delivery of each signal.


1 Answers

In addition to alk's answer:

You can use a per-thread function pointer to choose which function is executed when a certain signal is delivered, on a per-thread manner.

Note: Signals are delivered to any thread that is not explicitly blocking its delivery. This does not change that. You still need to use pthread_kill() or similar mechanisms to direct the signal to a specific thread; signals that are raised or sent to the process (instead of a specific thread), will still be handled by a random thread (among those that do not block it).

I cannot think of any use case where I'd personally prefer this approach; thus far there has always been some other way, something else easier and better. So, if you are considering implementing something like this for a real application, please step back and reconsider your application logic.

But, since the technique is possible, here's how I might implement it:

#include <signal.h>

/* Per-thread signal handler function pointer.
 * Always use set_thread_SIG_handler() to change this.
*/
static __thread void (*thread_SIG_handler)(int, siginfo_t *, void *) = (void *)0;

/* Process-wide signal handler.
*/
static void process_SIG_handler(int signum, siginfo_t *info, void *context)
{
    void (*func)(int, siginfo_t *, void *);

#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
    func = __atomic_load_n(&thread_SIG_handler, __ATOMIC_SEQ_CST);
#else
    func = __sync_fetch_and_add(&thread_SIG_handler, (void *)0);
#endif

    if (func)
        func(signum, info, context);
}

/* Helper function to set new per-thread signal handler
*/
static void set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *))
{
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
    __atomic_store_n(&thread_SIG_handler, func, __ATOMIC_SEQ_CST);
#else
    void (*oldfunc)(int, siginfo_t *, void *);
    do {
        oldfunc = thread_SIG_handler;
    } while (!__sync_bool_compare_and_swap(&thread_SIG_handler, oldfunc, func));
#endif
}

/* Install the process-wide signal handler.
*/
int install_SIG_handlers(const int signum)
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_sigaction = process_SIG_handler;
    act.sa_flags = SA_SIGACTION;
    if (sigaction(signum, &act, NULL))
        return errno;
    return 0;
}

I like the above because it does not require pthreads, and is very robust and reliable. Aside from the visual mess due to having that preprocessor logic to select which style of atomic built-ins are used, it's very simple too, if you look at it carefully.

GCC 4.7 and later provide C++11-like __atomic built-ins, older GCC versions and other compilers (ICC, Pathscale, Portland Group) provide __sync legacy built-ins. The __thread keyword for thread-local storage should similarly be available in all current POSIX-y systems.

If you have an archaic system, or insist on standards compliance, the following code should have roughly equivalent behaviour:

#include <pthread.h>
#include <signal.h>
#include <errno.h>

static pthread_key_t  thread_SIG_handler_key;

static void process_SIG_handler(int signum, siginfo_t *info, void *context)
{
    void (*func)(int, siginfo_t *, void *);

    *((void **)&func) = pthread_getspecific(thread_SIG_handler_key);
    if (func)
        func(signum, info, context);
}

static int set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *))
{
    sigset_t block, old;
    int result;

    sigemptyset(&block);
    sigaddset(&block, SIG); /* Use signal number instead of SIG! */
    result = pthread_sigmask(SIG_BLOCK, &block, &old);
    if (result)
        return errno = result;

    result = pthread_setspecific(thread_SIG_handler_key, (void *)func);
    if (result) {
        pthread_sigmask(SIG_SETMASK, &old, NULL);
        return errno = result;
    }

    result = pthread_sigmask(SIG_SETMASK, &old, NULL);
    if (result)
        return errno = result;

    return 0;
}

int install_SIG_handlers(const int signum)
{
    struct sigaction act;
    int result;

    result = pthread_key_create(&thread_SIG_handler_key, NULL);
    if (result)
        return errno = result;

    sigemptyset(&act.sa_mask);
    act.sa_sigaction = process_SIG_handler;
    act.sa_flags = SA_SIGACTION;
    if (sigaction(signum, &act, NULL))
        return errno;

    return 0;
}

I think the closest real-life equivalent to code like this that I've actually ever used, is one where I used one realtime signal (SIGRTMIN+0) blocked in all but one thread, as a reflector: it sent another realtime signal (SIGRTMIN+1) to a number of worker threads, in order to interrupt blocking I/O. (It is possible to do this with a single realtime signal, but the two-signal model is simpler to implement and easier to maintain.)

Such signal reflection or fanout is sometimes useful, and it's not that different from this approach. Different enough to warrant its own question, though, if someone is interested.

like image 76
Nominal Animal Avatar answered Sep 28 '22 04:09

Nominal Animal