Main Page | Modules | Data Structures | File List | Data Fields | Globals | Related Pages

Pattern reference

Below are a list of signal handling patterns with associated safety, performance, and portability notes.
  1. Calling async signal-unsafe functions from signal handlers.
     void unsafe_sighandler_a(int signum) {
         printf("Received signal %d\n", signum);
     }
    

     void unsafe_sighandler_b(int signum) {
         mylist->tail = (struct mylist*) malloc(sizeof(mylist));
         ...
     }
    
    SUSv3 (the Single UNIX Specification, version 3) defines a list of functions which are safe from any time from signal handlers. It's a very short list. In particular, you must not call malloc(3) from a signal handler, or any function which depends on it. Failures are rare enough that people think their code is correct, but this can lead to subtle bugs.
  2. Polling for a variable before system calls and on EINTR:
     volatile sig_atomic_t signal_received;
    
     void sighandler(int) { signal_received++; }
    
     ...
    
     int retval;
     do {
         if (signal_received) { handle_signal(); }
     } while ((retval = syscall()) == -1 && errno == EINTR) ;
    
    In this pattern, there is a race condition between the check for signal_received and syscall() actually entering kernel space. If a signal arrives in this time, it will not force EINTR and the signal delivery could be delayed indefinitely.
  3. Using a sigjmp_buf to immediately return from system calls.
     volatile sig_atomic_t signal_received, jump_is_safe;
     sigjmp_buf env;
    
     void sighandler(int) {
         signal_received++;
         if (jump_is_safe)
             siglongjmp(env, 1);
     }
    
     ...
    
     sigsetjmp(signal_received, 1);
     jump_is_safe = 1;
     if (!signal_received) {
         retval = syscall();
     }
     jump_is_safe = 0;
    
    This has a different race condition: if a signal arrives, it is impossible to tell if the system call completed and, if so, what its result was. This affects different calls differently: This also relies on jumping from a signal handler to be safe; this is not defined by SUSv3 and notably is false on Cygwin. Linux and Solaris do support this behavior, though neither correctly restores the cancellation state. (This requirement is shared by sigsafe.)

    It's also very hard to implement correctly. Notice several things about the code fragment above:

    These are all important!

    Also there's a performance problem - sigsetjmp(..., 1) makes a system call to retrieve the signal mask, so you're slowing down every iteration for correct signal behavior. To avoid that, you'd have to think about the signal mask yourself. Even more opportunities for bugs.

  4. Using pselect(2). This function is supposed to change the signal mask atomically in the kernel for the duration of operation, supporting error-free operation like this:
     sigset_t blocked, unblocked;
     int retval;
    
     pthread_sigmask(SIG_SETMASK, &blocked, NULL);
    
     ...
    
     while ((retval = pselect(..., &unblocked)) == -1 && errno == EINTR) {
         printf("Signal received.\n");
     }
    
     ...
    
    However, some implementations (notably Linux!) are wrong --- they simply surround a select(2) call with pthread_sigmask(2) calls. Thus, pselect(2) may not return EINTR when you expect it to.
  5. Replacing blocking IO calls with poll(2) calls and non-blocking IO calls:
     int signal_pipe[2];
    
     void sighandler(int signo) { write(signal_pipe[1], &signo, sizeof(int)); }
    
     ...
    
     struct pollfd fds[2] = {
         {fd,             POLLIN, 0},
         {signal_pipe[0], POLLIN, 0}
     };
    
     retval = poll(fds, 2, 0);
     ...
     if (fds[1] & POLLIN) { ...; handle_signals(); }
     ...
     retval = read(fd, buf, count);
    
    This method is correct but slow, since it doubles the number of system calls to be made on basic IO operations.
  6. Thread cancellation. In theory, thread cancellation allows for correct operation. In practice, no libc has an acceptable implementation. See my cancellation tests for details, but it will be a long time before this is an acceptable option.
  7. ...and many other schemes.

The solution

With sigsafe, you can write code like this:

 void myhandler(int signum, siginfo_t *info, void *ctx, intptr_t user_data) {
     sigaddset((sigset_t*) user_data, signum);
 }

 int main(void) {
     ...
     sigsafe_install_handler(SIGUSR1, &myhandler);
     sigsafe_install_handler(SIGUSR2, &myhandler);
     ...
  }

  ...

 void* thread_entry(void *arg) {
     ...
     sigsafe_install_tsd((intptr_t) malloc(sizeof sigset_t), &free);
     ...
 }

 void read_some_data(void) {
     int retval;

     while ((retval = sigsafe_read(fd, buf, count)) == -EINTR) {
         sigset_t *received = (sigset_t*) sigsafe_clear_received();
         if (sigismember(received, SIGUSR1)) {
             printf("Received USR1 signal\n");
         }
         if (sigismember(received, SIGUSR2)) {
             printf("Received USR2 signal\n");
         }
     }
     ...
 }

Note:
This is not the One True Method for correct signal handling. In particular, there are two other methods you should consider:
  1. handling all signals in a single thread. If you do not use thread-directed signals for internal signaling (timeouts, etc.), blocking signals everywhere and using sigwaitinfo(2) may be your easiest correct way.
  2. Handling signals with polling functions. If you exclusively use non-blocking IO, kevent(2)'s built-in signal mechanism or the pipe-write-from-signal-handler methods may work well for you.
Warning:
The sigsafe library is non-portable! Everything here relies on alternate system call wrappers implemented in assembly and a signal handler which adjusts the instruction pointer when signals arrive in system calls. This means that there is significant work involved in porting it to a new platform (where platform is a combination of OS and architecture).
Additionally, it makes the same assumption as all other methods for handling thread-directed signals (with the exception of kevent(2) handling), that pthread_getspecific(2) is async signal-safe. This is not guaranteed by SUSv3.

Legal information:
sigsafe is copyright © 2004 Scott Lamb <slamb@slamb.org>. It is released under the MIT license. See the README file for the full license text.
Generated on Fri Feb 4 11:13:32 2005 for sigsafe by doxygen 1.3.5