libstddjb
libskarnet
skalibs
Software
skarnet.org

The selfpipe library interface

The selfpipe functions are declared in the skalibs/selfpipe.h header and implemented in the libskarnet.a or libskarnet.so library.

What does it do ?

Signal handlers suck.

They do. I don't care how experienced you are with C/Unix programming, they do. You can be Ken Thompson, if you use signal handlers as a regular part of your C programming model, you are going to screw up, and write buggy code.

Unix is tricky enough with interruptions. Even when you have a single thread, signals can make the execution flow very non-intuitive. They mess up the logic of linear and structured code, they introduce non-determinism; you always have to think "and what if I get interrupted here and the flow goes into a handler...". This is annoying.

Moreover, signal handler code is very limited in what it can do. It can't use any non-reentrant function! If you call a non-reentrant function, and by chance you were precisely in that non-reentrant function code when you got interrupted by a signal... you lose. That means, no malloc(). No bufferized IO. No globals. The list goes on and on.
If you're going to catch signals, you'll want to handle them outside the signal handler. You actually want to spend the least possible time inside a signal handler - just enough to notify your main execution flow that there's a signal to take care of.

And, of course, signal handlers don't mix with event loops, which is a classic source of headaches for programmers and led to the birth of abominations such as pselect. So much for the "everything is a file" concept that Unix was built on.

A signal should be an event like any other. There should be a unified interface - receiving a signal should make some fd readable or something.

And that's exactly what the self-pipe trick, invented by DJB, does.

As long as you're in some kind of event loop, the self-pipe trick allows you to forget about signal handlers... forever. It works this way:

  1. Create a pipe p. Make both ends close-on-exec and nonblocking.
  2. Write a tiny signal handler ("top half") for all the signals you want to catch. This signal handler should just write one byte into p[1], and do nothing more; ideally, the written byte identifies the signal.
  3. In your event loop, add p[0] to the list of fds you're watching for readability.

When you get a signal, a byte will be written to the self-pipe, and your execution flow will resume. When you next go through the event loop, p[0] will be readable; you'll then be able to read a byte from it, identify the signal, and handle it - in your unrestricted main environment (the "bottom half" of the handler).

The selfpipe library does it all for you - you don't even have to write the top half yourself. You can forget their existence and recover some peace of mind.

Note that in an asynchronous event loop, you need to protect your system calls against EINTR by using safe wrappers.

How do I use it ?

Starting

int fd = selfpipe_init() ;

selfpipe_init() sets up a selfpipe. You must use that function first.
If fd is -1, then an error occurred. Else fd is a non-blocking descriptor that can be used in your event loop. It will be selected for readability when you've caught a signal.

Trapping signals

int r = selfpipe_trap(SIGTERM) ;

selfpipe_trap() catches a signal and sends it to the selfpipe. Uncaught signals won't trigger the selfpipe. r is 1 if the operation succeeded, and 0 if it failed. If it succeeded, you can forget about the trapped signal entirely.
In our example, if r is 1, then a SIGTERM will instantly trigger readability on fd.

int r ;
sigset_t set ;
sigemptyset(&set) ;
sigaddset(&set, SIGTERM) ;
sigaddset(&set, SIGHUP) ;
r = selfpipe_trapset(&set) ;

selfpipe_trap() handles signals one by one. Alternatively (and often preferrably), you can use selfpipe_trapset() to directly handle signal sets. When you call selfpipe_trapset(), signals that are present in set will be caught by the selfpipe, and signals that are absent from set will be uncaught. r is 1 if the operation succeeded and 0 if it failed.

Handling events

int c = selfpipe_read() ;

Call selfpipe_read() when your fd is readable. That's where you write your real signal handler: in the body of your event loop, in a "normal" context.
c is -1 if an error occurred - in which case chances are it's a serious one and your system has become very unstable. c is 0 if there are no more pending signals. If c is positive, it is the number of the signal that was caught.

Accessing the selfpipe

int fd = selfpipe_fd() ;

Sometimes you need to access the fd of the selfpipe in two very distinct translation units (typically to poll on it), and you rightly don't want to add a global variable to store it, especially since it's already stored in a global internal variable in skalibs. No need to bloat your binary anymore: selfpipe_fd() will now retrieve the value for you, wherever you are.

Finishing

selfpipe_finish() ;

Call selfpipe_finish() when you're done using the selfpipe. Signal handlers will be restored to SIG_DFL, i.e. signals will not be trapped anymore.

Any limitations ?

Some, as always.

Hey, Linux has signalfd() for this !

Yes, the Linux team loves to gratuitously add new system calls to do things that could already be done before without much effort. This adds API complexity, which is not a sign of good engineering.

However, now that signalfd() exists, it is indeed marginally more efficient than a pipe, and it saves one fd: so the selfpipe library is implemented via signalfd() when this call is available.