I recently found myself trying to port a program that uses Boost Asio to run on OpenBSD. Everything compiled OK but while running it would occasionally exit with an unhandled SIGPIPE
signal. This doesn’t happen on Linux. What’s going on here?
SIGPIPE
is a synchronous signal that’s sent to a process (thread in POSIX.1-2004) which attempts to write data to a socket or pipe that has been closed by the reading end. Importantly it’s not an asynchronous signal that notifies you when the reading end has been closed: it’s delivered only when you attempt to write data. In fact it’s generated precisely when the system call (write(2)
, sendmsg(2)
, etc.) would fail with EPIPE
and doesn’t give any additional information.
So what’s the point then? The default action for SIGPIPE
is to terminate the process without a core dump (just like SIGINT
or SIGTERM
). This simplifies error handling in programs that are meant to run as part of a shell pipeline: reading input, transforming it, and then writing it to another process. SIGPIPE
allows the program to skip error handling and blindly write data until it’s killed.
For programs that handle write errors it doesn’t seem to be useful and is best avoided. But unfortunately there are several different ways to do that.
Ignore the signal globally
This is the easiest if you are in complete control of the program (i.e. not writing a library). Just set the signal to SIG_IGN
and forget about it.
signal(SIGPIPE, SIG_IGN);
Use MSG_NOSIGNAL
If you are writing to a socket, and not an actual pipe, pass the MSG_NOSIGNAL
flag to send(2)
or sendmsg(2)
. This has been in Linux for ages and was standardised in POSIX.1-2008 so it’s available almost anywhere.
Set SO_NOSIGPIPE socket option
This is a bit niche as it only exists on FreeBSD and OS X. Use setsockopt(2)
to set this option on a socket and all subsequent send(2)
calls will behave as if MSG_NOSIGNAL
was set.
int on = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on))
This seems to be of limited utility as calling write(2)
on the socket will still generate SIGPIPE
. The only use I can think of is if you need to pass the socket to a library or some other code you don’t control.
Temporarily mask the signal on the current thread
The most general solution, for when you are not in full control of the program’s signal handling and want to write data to an actual pipe or use write(2)
on a socket, is to first mask the signal for the current thread with pthread_sigmask(3)
, write the data, drain any pending signal with sigtimedwait(2)
and a zero timeout, and then finally unmask SIGPIPE
. This technique is described in more detail here. Note that some systems such as OpenBSD do not have sigtimedwait(2)
in which case you need to use sigpending(2)
to check for pending signals and then call the blocking sigwait(2)
.
Anyway back to the original problem. Asio hides SIGPIPE
from the programmer by either setting the SO_NOSIGPIPE
socket option on systems that support it, or on Linux by passing MSG_NOSIGNAL
to sendmsg(2)
. None of these apply to OpenBSD which is why we get the SIGPIPE
. I submitted a pull request to pass MSG_NOSIGNAL
on OpenBSD as well. But I don’t know when or if that will be merged so I’m also trying to get the same fix added to the ports tree.
UPDATE: a patch is now in the ports tree.