The self-pipe trick lets a program waiting for I/O readiness notice that a signal has arrived. A minimal signal handler writes a notification byte to a pipe; the event loop watches the pipe’s read end and handles the actual work in normal program flow. It is a Unix-oriented way to prevent a signal from getting lost between checking state and beginning a wait.
How the self-pipe trick works
A process may wait in select() for a socket or other descriptor to become ready while a signal arrives asynchronously. If the handler only sets a flag, a race can occur: the signal arrives after the program checks the flag but before it calls select(). The handler returns, and the program may then wait without noticing the pending work.
A self-pipe makes the signal visible to the event loop as descriptor readiness. The handler writes a small byte to the pipe’s write end. Because the read end is in the event wait set, the loop wakes when data is available, drains the pipe, and performs the application’s response outside the handler. The Linux select(2) manual describes this technique for systems without pselect().
Implementing a self-pipe safely
- Create and configure the pipe. Make both ends nonblocking before installing the signal handler. Nonblocking mode prevents a handler write from hanging if the pipe fills and lets the event loop drain data without waiting for more.
- Install a minimal handler. Have it write a small notification byte to the pipe’s write end. Avoid performing the application’s main work in signal context.
- Watch the read end. Add the pipe’s read descriptor to the event loop’s wait set along with the descriptors it already monitors.
- Drain and respond in normal flow. When the read end is reported readable, read available bytes until the nonblocking pipe is empty, then inspect pending state and carry out the appropriate work.
- Integrate interruption and cleanup. Handle interrupted waits according to the event loop’s design, and close both descriptors when that loop’s lifecycle ends.
Michael Kerrisk’s 2006 explanation of the self-pipe technique likewise emphasizes draining the pipe because multiple signals may occur. A byte is a wake-up notification, not necessarily a reliable count of signals: design the loop to determine what work is actually pending rather than assuming one byte corresponds to exactly one event.
Recommended Free Tools
#1 Best Overall
Self-pipe versus masked-wait alternatives
The alternatives differ in how they integrate signal masking with the event wait. Availability and suitability depend on the target platform and the event API already used by the program.
| Approach | How it addresses the wait race | Integration and trade-offs |
|---|---|---|
| Self-pipe | Turns a signal into readiness on a pipe descriptor. | Fits a descriptor-based loop, including one built around select(), but requires pipe setup, nonblocking I/O, draining, and descriptor lifecycle management. |
pselect() |
Atomically changes the signal mask while waiting, addressing the check-then-wait race. | Can avoid the extra pipe machinery when available and appropriate for the loop. |
ppoll() or epoll_pwait() |
Provide related masked-wait approaches. | May suit programs already using the corresponding polling interface; verify availability and platform semantics. |
Kerrisk characterized the self-pipe procedure in 2006 as “Works, and is portable, but complex.” That is his assessment of the procedure, not a guarantee of identical behavior across operating systems. The Linux manual identifies POSIX.1-2024 as the standard for its select()/pselect() interface; check the target system’s signal and event API documentation before treating surrounding details as portable.
Platform and event-loop considerations
The technique is Unix-oriented, not a universal recipe for every runtime. For example, the Python Software Foundation’s Python 3.14 select documentation says that on Windows, select() works with sockets, not arbitrary file descriptors. Do not assume a Unix pipe can be monitored there in the same way.
Also consider the readiness API, not just the signal mechanism. The Python documentation describes select() as O(highest file descriptor) and poll() as O(number of file descriptors). For loops with many descriptors, that scaling distinction may affect the choice of event API; it does not change the self-pipe’s basic role as a wake-up notification.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsQuick Recap
Best Value
Rank #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.

