skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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;
    }
    

  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search