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 (
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
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.
If you are writing to a socket, and not an actual pipe, pass the
MSG_NOSIGNAL flag to
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
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
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.