My problem is that I'm working on a portable library to be usable at least on Linux, macOS/OpenBSD and Microsoft Windows platforms in a threaded environment and using OpenSSL. signal(SIGPIPE, SIG_IGN)
is not an option because it modifies the users process environment. I could use a confusing case distinction with using SO_NOSIGPIPE
on macOS/OpenBSD, MSG_NOSIGNAL
on Linux platforms and do nothing on MS Windows. This is only usable with system call send()
but OpenSSL uses write()
instead. An issue SIGPIPE when OpenSSL is used with a socket is pending since years on OpenSSL developement.
So I had a look at the general solution from kroki. He wrote that he uses sigtimedwait()
but that isn't available on macOS/OpenBSD. Fortunately it can be workaroud with sigpending()
and sigwait()
. Looking at krokis example to understand how it works I create a "scoped" class CSigpipe with only constructor and destructor. For details have a look at the references. Here is the class:
Header file sigpipe.hpp
#ifndef SAMPLE_SIGPIPE_HPP
#define SAMPLE_SIGPIPE_HPP
#ifndef _MSC_VER
#include <csignal>
namespace sigpipe {
class CSigpipe_scoped {
public:
CSigpipe_scoped();
~CSigpipe_scoped();
private:
bool m_sigpipe_pending;
bool m_sigpipe_unblock;
};
} // namespace sigpipe
#define SCOPED_NO_SIGPIPE sigpipe::CSigpipe_scoped sigpipe;
#else
// This will the ported program also compile on win32 without error.
#define SCOPED_NO_SIGPIPE
#endif // #ifndef _MSC_VER
#endif // SAMPLE_SIGPIPE_HPP
Source file sigpipe.cpp
#ifndef _MSC_VER
#include "sigpipe.hpp"
namespace sigpipe {
CSigpipe_scoped::CSigpipe_scoped() {
/* We want to ignore possible SIGPIPE that we can generate on write. SIGPIPE
* is delivered *synchronously* and *only* to the thread doing the write. So
* if it is reported as already pending (which means the thread blocks it),
* then we do nothing: if we generate SIGPIPE, it will be merged with the
* pending one (there's no queuing), and that suits us well. If it is not
* pending, we block it in this thread (and we avoid changing signal action,
* because it is per-process). */
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
m_sigpipe_pending = sigismember(&pending, SIGPIPE);
if (!m_sigpipe_pending) {
sigset_t sigpipe_mask;
sigemptyset(&sigpipe_mask);
sigaddset(&sigpipe_mask, SIGPIPE);
sigset_t blocked;
sigemptyset(&blocked);
pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &blocked);
/* Maybe is was blocked already? */
m_sigpipe_unblock = !sigismember(&blocked, SIGPIPE);
}
}
CSigpipe_scoped::~CSigpipe_scoped() {
/* If SIGPIPE was pending already we do nothing. Otherwise, if it become
* pending (i.e., we generated it), then we sigwait() it (thus clearing
* pending status). Then we unblock SIGPIPE, but only if it were us who
* blocked it. */
if (!m_sigpipe_pending) {
sigset_t sigpipe_mask;
sigemptyset(&sigpipe_mask);
sigaddset(&sigpipe_mask, SIGPIPE);
// I cannot use sigtimedwait() because it isn't available on
// macOS/OpenBSD. I workaround it with sigpending() and sigwait() to
// ensure that sigwait() never blocks.
sigset_t pending;
while (true) {
/* Protect ourselves from a situation when SIGPIPE was sent by
* the user to the whole process, and was delivered to other
* thread before we had a chance to wait for it. */
sigemptyset(&pending);
sigpending(&pending);
if (sigismember(&pending, SIGPIPE)) {
int sig; // Only return buffer, not used.
sigwait(&sigpipe_mask, &sig);
} else
break;
}
if (m_sigpipe_unblock)
pthread_sigmask(SIG_UNBLOCK, &sigpipe_mask, nullptr);
}
}
} // namespace sigpipe
#endif
A simple main example program.
#include <iostream>
#ifdef _MSC_VER
// This simple test doesn't make sense on Microsoft Windows because it doesn't
// support sigpipe. It doesn't know the signal name SIGPIPE and never raise a
// signal with broken pipe on socket write(). But it compiles the object
// "instantiation" without error to be portable.
#include "sigpipe.hpp"
int main() {
SCOPED_NO_SIGPIPE
std::cout << "Nothing done.\n";
}
#else
#include "sigpipe.hpp"
namespace {
volatile std::sig_atomic_t gSignalStatus;
}
void signal_handler(int signal) { gSignalStatus = signal; }
int main() {
// Install a signal handler
std::signal(SIGPIPE, signal_handler);
// Suppress signal
std::cout << "SignalValue: " << gSignalStatus << '\n';
std::cout << "Suppress signal SIGPIPE\nRaise SIGPIPE = " << SIGPIPE << '\n';
{
SCOPED_NO_SIGPIPE
if (std::raise(SIGPIPE) != 0)
return EXIT_FAILURE; // Signal will be restored
} // Signal will be restored
if (gSignalStatus != 0)
std::cout << "Signal delivered, signal_handler executed." << '\n';
std::cout << "SignalValue: " << gSignalStatus << '\n';
// Not suppressing signal
std::cout << "\nRaise SIGPIPE = " << SIGPIPE << '\n';
std::raise(SIGPIPE);
if (gSignalStatus != 0)
std::cout << "Signal delivered, signal_handler executed." << '\n';
std::cout << "SignalValue: " << gSignalStatus << '\n';
return EXIT_SUCCESS;
}
#endif
Compiling on Linux with:
~$ g++ -std=c++17 -Wall -Wpedantic -Wextra -Werror -Wuninitialized -Wsuggest-override -Wdeprecated sigpipe.cpp main.cpp -lpthread
Output of the simple example on Linux:
~$ a.out
SignalValue: 0
Suppress signal SIGPIPE
Raise SIGPIPE = 13
SignalValue: 0
Raise SIGPIPE = 13
Signal delivered, signal_handler executed.
SignalValue: 13
References:
SIGPIPE and how to ignore it
Ignore SIGPIPE without affecting other threads in a process
How to prevent SIGPIPEs (or handle them properly)
signals, threads, SIGPIPE, sigpending (fun!)
signal
is actually taking place? Are you using threads? – Tribadismsignal
occur before any threads are created? Are you 100% sure? – Tribadismmain
starts that creates a thread. – Tribadism