Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The self-pipe trick turns a Unix signal into ordinary file-descriptor readiness: a minimal signal handler writes a byte to a nonblocking pipe, and the event loop responds after the pipe becomes readable. This lets a program wait for signals alongside sockets and other descriptors without doing complex or unsafe work inside the handler.

What the self-pipe trick does

A signal may arrive just after a program checks a flag but before it calls select() or poll(). The handler sets the flag and returns; then the program enters the wait and can sleep indefinitely even though the signal has already happened. A pipe avoids that lost-wakeup race: the handler writes a byte, which remains queued as readable input until the event loop consumes it.

D. J. Bernstein described the core idea as maintaining a pipe, selecting for readability on its input, and writing a byte from the SIGCHLD handler. See Bernstein’s self-pipe note. The pipe does not make arbitrary handler work safe; it gives the handler a small way to notify normal program logic.

How to implement it safely

  1. Create the pipe before installing the handler. This ensures the handler cannot run before its write descriptor exists. The Linux Programming Interface explicitly notes this ordering prevents a race.
  2. Make both ends nonblocking. The handler must never wait for space in a full pipe. Configure the descriptors with the platform’s supported nonblocking mechanism.
  3. Register the read end with the event loop. Monitor it using select(), poll(), or, where available, epoll_wait().
  4. Install a minimal handler. Have it call the async-signal-safe write() function to place a byte in the pipe. If the handler changes errno, save and restore its previous value so interrupted code does not observe an unintended change.
  5. Drain the pipe when it is readable. Read repeatedly until nonblocking read() fails with EAGAIN. Treat the bytes as a wake-up notification, not as a guaranteed count of signals.
  6. Handle the signal in ordinary event-loop context. Perform cleanup, update application state, reap child processes when appropriate, or log only after leaving the handler.

Michael Kerrisk’s The Linux Programming Interface explains that write() is safe to use in the handler because it is async-signal-safe, and that the pipe should be drained until EAGAIN. Its discussion also notes that the technique works with poll() and epoll_wait(), not only select().

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What the handler should—and should not—do

Keep the handler tiny. A write to the nonblocking pipe is a notification; it is not the place to perform the requested operation. Avoid malloc(), buffered stdio, logging frameworks, and other routines that are not async-signal-safe. Deferring real work to the event loop avoids unsafe interactions with interrupted code and shared program state.

What happens when signals arrive in bursts

The pipe is a finite buffer. If it fills, a nonblocking handler write can fail with EAGAIN. Do not retry by blocking in the handler: unread bytes already make the read end ready, so the event loop has a notification to process. Drain promptly to restore room. Because notifications can coalesce or writes can fail once the pipe is full, the number of bytes read is not a reliable tally of how many signals were delivered. If the application needs to determine current conditions, inspect or reconcile its underlying state after waking rather than inferring an exact event count from pipe bytes.

Rank #2
Sale
The Unix Programming Environment (Prentice-Hall Software Series)
  • The Unix Programming Environment (Prentice-Hall Software Series)
  • Product Type: ABIS_BOOK
  • Pearson

skalibs warns that a self-pipe can theoretically fill if more than PIPE_BUF signals arrive before the loop reads it. Its documentation says PIPE_BUF is 4096 on most Unix systems, but that value is implementation-dependent; check the target platform rather than assuming it. In multithreaded programs, a process-wide signal policy also matters: skalibs advises care with a global self-pipe and describes dedicating a signal-handling thread while blocking the handled signals elsewhere as one approach.

Self-pipe, pselect(), or signalfd()?

Approach Portability How it integrates with waiting Main consideration
Self-pipe Uses ordinary Unix pipes and is broadly portable across Unix-like systems. The pipe’s read descriptor joins an existing select(), poll(), or epoll_wait() loop. Requires pipe setup, nonblocking I/O, draining, and a carefully limited handler; notifications are not an exact signal count.
pselect() POSIX interface; availability and historical library emulation have varied. Accepts a signal mask that is applied during the wait, expressing the signal-mask transition as part of waiting. Can address the check-then-wait race directly, but platform and library support should be verified.
signalfd() Linux-specific. Delivers signals through a file descriptor that can be monitored by an event loop. skalibs notes it can be marginally more efficient and save one descriptor compared with a self-pipe, at the cost of Linux-specificity.

Choose based on the guarantees and environment you need. pselect() is a direct fit when atomic signal-mask handling during the wait is central and the target systems support it reliably. A self-pipe is useful when the event loop already handles descriptors and Unix portability matters. On Linux-only software, signalfd() can make signals part of descriptor-based handling without a separate pipe. For any choice, account for thread signal masks and burst behavior rather than assuming signals are queued one-for-one.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Where the name came from

Bernstein says he recalls developing the technique around 1990 and describing it publicly on June 16 and August 25, 1991; he says he did not use the name “self-pipe trick” until several years later. The GNU Hurd documentation also points readers to Bernstein’s note as further reading on Unix signal handling; that page was last edited February 17, 2015.

Quick Recap

SaleBestseller No. 2
The Unix Programming Environment (Prentice-Hall Software Series)
The Unix Programming Environment (Prentice-Hall Software Series)
The Unix Programming Environment (Prentice-Hall Software Series); Product Type: ABIS_BOOK; Pearson
$76.05
SaleBestseller No. 3
SaleBestseller No. 4

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.