Reputation: 391
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.
Upvotes: 1
Views: 197
Reputation: 391
This is an example of forwarding SIGPIPE to the main thread - probably sufficient in my case:
#include <csignal>
#include <chrono>
#include <iostream>
#include <thread>
#include <signal.h>
pthread_t mainThread {pthread_self()};
void forwardSig(int sig)
{
if (not pthread_equal(pthread_self(), mainThread))
{
pthread_kill(mainThread, sig);
}
}
int main()
{
struct sigaction newAction {};
sigemptyset(&newAction.sa_mask);
newAction.sa_handler = forwardSig;
sigaction(SIGPIPE, &newAction, nullptr);
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;
}
Upvotes: 0
Reputation: 58524
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.
Upvotes: 1