22

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?

alk
  • 69,737
  • 10
  • 105
  • 255
tomKPZ
  • 827
  • 1
  • 10
  • 17
  • 3
    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. – Fred the Magic Wonder Dog Jul 08 '14 at 23:59
  • 4
    Using `signal` in a multithreaded program is essentially undefined behaviour. It says so in the manual. – Kerrek SB Jul 09 '14 at 00:02
  • 1
    Short answer, no, the signal handler is a process wide thing. – Spudd86 Jul 09 '14 at 00:21
  • 2
    Well you have figured out that pthread_kill will direct the signal to specified thread but obviously the error is in your code and there isn't anything we can do for you if you don't post it. `kill` however is going to deliver a signal to any random thread unless you take measures (e.g. block them in other threads) to direct them to the desired thread. – Duck Jul 09 '14 at 03:23

2 Answers2

12

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.

Community
  • 1
  • 1
Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • You were absolutely right about rethinking my design. I've opted to use pthread_cancel as it seemed to fit my needs well. However, there might be a small problem in your listings. `pthread_getspecific()` is not async-signal safe according to the man page, and I don't think `__thread` storage access is either. – tomKPZ Jul 12 '14 at 07:48
  • @user1887231: In practice, [they are](http://sourceware.org/ml/libc-alpha/2012-06/msg00372.html). The standards haven't addressed the issue of async-signal safe thread-specific variables yet. At least with GNU tools, both `pthread_getspecific()` and `__thread` variables are async-signal safe *in practice*. (Here, the signal handler only *reads* the thread-specific variable, so "out-of-TLS-storage" cases do not apply.) If you worry about this, let me know, and I'll conjure up a workaround (I think I know of a way). – Nominal Animal Jul 12 '14 at 12:50
9

It is not possible to install "per-thread" signal handlers.

From man 7 signal (emphasis by me):

The signal disposition is a per-process attribute: in a multithreaded application, the disposition of a particular signal is the same for all threads.

It however is possible to direct each signal-type to a different thread, by masking out the reception of any number of signal-types on a "per-thread" base.

On how to direct a set of signal-types to a specific thread you might like to have look at this answer: https://stackoverflow.com/a/20728819/694576

Community
  • 1
  • 1
alk
  • 69,737
  • 10
  • 105
  • 255