I am trying to implement a graceful shutdown of a process when its output is being piped to another process. I am testing the code bellow by piping its output: ./a.out | less
and pressing q
when a prompt appears. Instead of expected completion of sigwait()
I see invocation of signal handler instead (it is added here just to show what is going on).
#include <csignal>
#include <chrono>
#include <iostream>
#include <thread>
#include <signal.h>
int handlerSig {0};
void signalHandler(int s)
{
handlerSig = s;
std::cerr << "handlerSig: " << handlerSig << std::endl;
}
int main()
{
for (int i = 1; i < 32; ++i)
{
std::signal(i, signalHandler);
}
bool run {true};
std::thread thread {[&]
{
while (run)
{
std::cout << "ping" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds {500});
}
}};
sigset_t waitSet;
sigemptyset(&waitSet);
sigaddset(&waitSet, SIGINT);
sigaddset(&waitSet, SIGPIPE);
sigaddset(&waitSet, SIGTERM);
pthread_sigmask(SIG_BLOCK, &waitSet, nullptr);
int waitSig {0};
sigwait(&waitSet, &waitSig);
run = false;
thread.join();
std::cerr << "waitSig: " << waitSig << std::endl;
}
I get consistent results on WSL2 and CentOS machine and I would prefer to focus on solving this problem there. When running under WSL1, neither SIGINT nor SIGTERM cause completion of sigwait()
unless I remove pthread_sigmask(SIG_BLOCK...)
, but that seems to contradict my understanding how sigwait()
is supposed to be used.
You'll need to contrive some other way of noticing that the write failed, for example, ignoring SIGPIPE but setting std::cout.exceptions(ios::badbit)
, or handling the signal within your writing thread.
Importantly, that SIGPIPE will always be generated for your writing thread, your sigwait()
ing thread notwithstanding. Certain signals arising from a thread's activity are generated exclusively for that thread, meaning they'll be delivered to or accepted by that thread only. (POSIX.1-2008 System Interfaces 2.4.1) Typically, "naturally occurring" SIGPIPEs, SIGFPEs, and SIGSEGVs work like this.