When write()
is called for a named or unnamed pipe or stream socket whose reading end is closed, two things happen:
SIGPIPE
signal is sent to the process that called write()
SIGPIPE
signal is sent to the thread that called write()
EPIPE
error is returned by write()
There are several ways to deal with SIGPIPE
:
SIGPIPE
may be disabled by setting platform-specific options like MSG_NOSIGNAL
in Linux and SO_NOSIGPIPE
in BSD (works only for send
, but not for write
). This is not portable.SIGPIPE
will not be generated if writer uses O_RDWR
instead of O_WRONLY
, so that reading end is always opened. However, this disables EPIPE
too.SIGPIPE
or set global handler. This is a good solution, but it's not acceptable if you don't control the whole application (e.g. you're writing a library).SIGPIPE
is send to the thread that called write()
and handle it using synchronous signal handling technique.Code below demonstrates thread-safe SIGPIPE
handling for POSIX.1-2004 and later.
It's inspired by this post:
SIGPIPE
to signal mask of current thread using pthread_sigmask()
.SIGPIPE
using sigpending()
.write()
. If reading end is closed, SIGPIPE
will be added to pending signals mask and EPIPE
will be returned.write()
returned EPIPE
, and SIGPIPE
was not already pending before write()
, remove it from pending signals mask using sigtimedwait()
.pthread_sigmask()
.Source code:
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <sys/signal.h>
ssize_t safe_write(int fd, const void* buf, size_t bufsz)
{
sigset_t sig_block, sig_restore, sig_pending;
sigemptyset(&sig_block);
sigaddset(&sig_block, SIGPIPE);
/* Block SIGPIPE for this thread.
*
* This works since kernel sends SIGPIPE to the thread that called write(),
* not to the whole process.
*/
if (pthread_sigmask(SIG_BLOCK, &sig_block, &sig_restore) != 0) {
return -1;
}
/* Check if SIGPIPE is already pending.
*/
int sigpipe_pending = -1;
if (sigpending(&sig_pending) != -1) {
sigpipe_pending = sigismember(&sig_pending, SIGPIPE);
}
if (sigpipe_pending == -1) {
pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
return -1;
}
ssize_t ret;
while ((ret = write(fd, buf, bufsz)) == -1) {
if (errno != EINTR)
break;
}
/* Fetch generated SIGPIPE if write() failed with EPIPE.
*
* However, if SIGPIPE was already pending before calling write(), it was
* also generated and blocked by caller, and caller may expect that it can
* fetch it later. Since signals are not queued, we don't fetch it in this
* case.
*/
if (ret == -1 && errno == EPIPE && sigpipe_pending == 0) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 0;
int sig;
while ((sig = sigtimedwait(&sig_block, 0, &ts)) == -1) {
if (errno != EINTR)
break;
}
}
pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
return ret;
}