Search code examples
cpthreadssignal-handling

What are the pitfalls of using `pause()` in signal handler?


I want to suspend the thread and resume it. There are few methods listed here. But I thought of using pause() library function from unistd.h.

What are the pitfalls of using pausing in signal handler?

One I noticed is, when I send 0 to pause thread and send 0 again then my signal is queued. I need to send 1 twice to resume the thread.

I guess there may be many more cases like this. How to handle such conditions if I want to use pause() or sleep() in signal handler.

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>

static bool thread_ready = false;

static void cb_sig(int signal)
{
        if (signal == SIGUSR1)
                pause();
        else if (signal == SIGUSR2)
                ;
}

static void *thread_job(void *ignore)
{
        int i = 0;
        struct sigaction act;

        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        act.sa_handler = cb_sig;

        if (sigaction(SIGUSR1, &act, NULL) == -1)
                printf("unable to handle siguser1\n");
        if (sigaction(SIGUSR2, &act, NULL) == -1)
                printf("unable to handle siguser2\n");

        thread_ready = true;
        while (1) {
                printf("thread counter: %d\n", i++);
                sleep(1);
        }

        return NULL;
}

int main()
{
        bool running;
        int user_input;
        pthread_t thread;

        if (pthread_create(&thread, NULL, thread_job, NULL))
                return -1;

        while (!thread_ready);

        for (running = true; running; ) {
                printf("0: pause thread, 1: resume thread, -1: exit\n");
                scanf("%d", &user_input);

                switch(user_input) {
                case -1:
                        running = false;
                        break;
                case 0:
                        pthread_kill(thread, SIGUSR1);
                        break;
                case 1:
                        pthread_kill(thread, SIGUSR2);
                        break;
                }
        }

        pthread_kill(thread, SIGKILL);
        return 0;
}

Solution

  • A signal handler should not sleep(), and probably should not pause(), even though technically, both of these functions are async-signal-safe. Signal handlers should run quickly and minimize or (preferrably) completely avoid blocking.

    As for specific pitfalls, you already noted one: by default, a signal is automatically blocked while its handler is running. It is possible to install the handler in a way that avoids that, but here that wouldn't help you: if you kept sending signals that get handled by your particular handler, then you would always have at least one thread blocked in that handler. If you kept sending them to the same thread then that thread would never unblock.

    More generally, there are any number of poor interactions that might happen between signal masks, signaling, and signal handlers blocking on signal receipt.

    Moreover, pause() is rather non-specific unless you combine it with setting a rather restrictive signal mask (in which case sigsuspend() is probably a better choice). But if you set a restrictive signal mask then you potentially interfere with other uses of signaling.

    Do not "handle" such issues other than by avoiding use of pause() and sleep() in your signal handlers.