kpx1894
kpx1894

Reputation: 391

SIGPIPE handling by sigwait

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

Answers (2)

kpx1894
kpx1894

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

pilcrow
pilcrow

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

Related Questions