libstddjb
skalibs
Software
www.skarnet.org

The selfpipe library interface

It's a part of libstddjb. The functions are defined in the selfpipe.h header.

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. Most of libstddjb's wrappers are there to protect system calls from EINTR. (And no, the SA_RESTART option in sigaction() isn't protection enough.) But signal handlers are more than just pesky interruptions: they can totally change the execution flow. 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 an 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 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 selfpipe library does it all for you - you don't even have to write the signal handlers yourself. You can forget their existence and recover some peace of mind. Of course, you still need to protect your system calls against EINTR: the self-pipe trick doesn't prevent signals from happening.

How do I use it ?

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.

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 0 if the operation succeeded, and -1 if it failed. If it succeeded, you can forget about the trapped signal entirely.
In our example, if r is 0, then a SIGTERM will instantly trigger readability on fd.

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.

selfpipe_finish() ;

Call selfpipe_finish() when you're done using the selfpipe. Signal handlers will be restored to their previous value.

Any limitations ?

Some, as always.