Richard
Richard

Reputation: 15552

Issue with timer with long signal handler (SIGALARM)

There is a timer which sends out signal SIGALARM every 1 sec. A signal handler which sleeps 2 sec is registered. What happens? Specifically, I have following code, in which the process runs multiple threads. It's quite interesting that with this long signal handler, it looks other threads are blocked from execution... Can anyone explain why this is the case?

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> //rand
#include <sys/wait.h>
#include <time.h>
#include <sys/time.h>
#include <pthread.h>
#define NUM_THREADS 2

int init_timer(int real_time, int msec) {
    struct itimerval timeslice;
    timeslice.it_interval.tv_sec = msec / 1000;
    timeslice.it_interval.tv_usec = (msec % 1000) * 1000;
    timeslice.it_value.tv_sec = msec / 1000;
    timeslice.it_value.tv_usec = (msec % 1000) * 1000;
    setitimer(real_time ? ITIMER_REAL : ITIMER_VIRTUAL, &timeslice, NULL);
    return 0;
}

void install_handler(int signo, void(*handler)(int)) {
    sigset_t set;
    struct sigaction act;

    /* Setup the handler */
    act.sa_handler = handler;
    act.sa_flags = SA_RESTART;
    sigaction(signo, &act, 0);

    /* Unblock the signal */
    sigemptyset(&set);
    sigaddset(&set, signo);
    sigprocmask(SIG_UNBLOCK, &set, NULL);
    return;
}

void timerTest(int signo)
{
    printf("000\n");
    sleep(1);
    printf("111\n");
}

void * threadTest(void * threadId)
{
    while(true)
    {
        printf("222\n");
    }
}

int main(int argc, char *argv[]) {
    int real_time = 1;
    int tick_msec = 10;
    init_timer(real_time, tick_msec);
    install_handler(real_time ? SIGALRM : SIGVTALRM, &timerTest);

    pthread_t threads[NUM_THREADS];
    int rc;
    long t;
    for (t = 0; t < NUM_THREADS; t++) {
        rc = pthread_create(&threads[t], NULL, threadTest, (void *) t);
        if (rc) {
            exit(-1);
        }
    }

    void * status;
    for (t = 0; t < NUM_THREADS; t++) {
        rc = pthread_join(threads[t], &status);
        if (rc) {
            exit(-1);
        }
    }
    pthread_exit(NULL);
}

printout:

222
222
222
222
...
222
000
111
000
111
...

there will be no 222 after the first 111 occurs? why so?

Upvotes: 3

Views: 2220

Answers (3)

Konstantin
Konstantin

Reputation: 3159

It's incorrect to use printf in a signal handler.

Regarding the thread that is going to handle the SIGALARM you may find some useful information on signal blocking here. The post contains enlightening Linux kernel code. Essentially the signal is handled by the main thread if it wants to receive it. If not, it will be handled by any other thread that wants it. You could mask specific signals in a thread using pthread_sigmask(3)

Upvotes: -1

Jean-Paul Calderone
Jean-Paul Calderone

Reputation: 48325

The signal is delivered to a particular thread, so the signal handler runs in a particular thread (the thread the signal was delivered to). If the signal is delivered to a thread writing out 222\n, then that thread must stop writing out 222\n and run the signal handler. Your example signal handler takes a full second to run, so that's a full second during which that thread may not write out 222\n.

Additionally, since you are using printf to write out all of these bytes, there is some locking being done in libc. Since printf is not an "async signal safe" function, it's actually undefined what happens if you use it in a signal handler. One possible explanation for the behavior you observe is this. If the signal is delivered to a thread while that thread holds the stdout lock, then no other thread will be able to write to stdout until the handler returns and the lock can be released by the "normal" code running in that thread. The signal handler can still write to stdout in this case, though, because the lock is an rlock which can be acquired repeatedly any particular thread. This may vary from depending on the specific platform, C library, thread library, or phase of the moon, though. Your example is easily converted to use write(2) though, which demonstrates more or less the same problem behavior, has more or less the same fix, and doesn't rely on undefined behavior.

If you SIG_BLOCK the timer signal in the 222\n threads, then the signal handler will always run in the main thread and you will continue to get 222\n output while the signal handler sleeps.

Seth also makes a great point about only using the safe functions in signal handlers. Using any others means your program's behavior is undefined.

Upvotes: 4

Seth Robertson
Seth Robertson

Reputation: 31441

Often times, signals (or at least that signal) is blocked when you are in a signal handler. It is also a bad idea to do very much in a signal handler. Generally you should set a variable or something like that, and then deal with the signal once you are in your normal codepath.

See sigaction's SA_NODEFER flag for a way to permit or deny receiving a signal inside a signal handler.

There is also a limited number of functions which are safe to call from inside a signal handler. signal(7) man page describes this. "A signal handler function must be very careful, since processing elsewhere may be interrupted at some arbitrary point in the execution of the program. POSIX has the concept of "safe function". If a signal interrupts the execution of an unsafe function, and handler calls an unsafe function, then the behavior of the program is undefined"

A program which calls an unsafe function from inside a signal handler is broken. On some machines it will "work"; on others it will coredump. It is permitted to do anything or nothing, including reformatting the disk, subjecting the user to scratchy Barry Manilow records played backwards, or dropping a tacnuke on Dallas. Attempting to call an unsafe function puts the program into the twilight zone of undefined behavior.

With apologies to der Mouse.

Upvotes: 2

Related Questions