Search code examples
c++multithreadingsignalsrace-condition

Does using sigwait and signalfd concurrently in a multithreaded program result in a race condition?


I am writing a multi-threaded program where, among other things, I have a thread listening to a socket for incoming network connections. To allow this to be interrupted, I am using poll in combination with signalfd (rather than a raw await call). However, I also have other threads that I need to be able to notify of potential interrupts, so I am using a sigwait call in a dedicated thread to wait for signals. My attempt to get the race to occur is in the code below:

int main()
{
    int sigval = 0;

    sigset_t mask;
    sigemptyset (&mask);
    sigaddset (&mask, SIGINT);
    pthread_sigmask(SIG_BLOCK, &mask, nullptr);
    
    int sfd = signalfd(-1, &mask, SFD_NONBLOCK);

    auto handler = std::thread([&] {
        int signal;
        sigwait(&mask, &signal);
        sigval = signal;
    });

    pollfd pfd[1] = {{.fd=sfd, .events=POLLIN}};

    int ret = poll(pfd, 1, -1);

    std::cout << "Poll returned with revents = " << pfd[0].revents << std::endl;

    handler.join();
    
    std::cout << "Handled thread set sigval to: " << sigval << std::endl;
    
    return 0;

}

Every time I run this and kill it with SIGINT, it appears to work, in that the poll call returns, and sigval is set by the handler thread. However, my understanding is that sigwait consumes the signal, while signalfd does not. So, if somehow sigwait was called before signalfd received notification of the signal, this program could potentially hang forever (with poll waiting for a signal that isn't coming). I assume since I can't manage to get the program to hang that there is something on beneath the hood that prevents this from happening, but can I guarantee that this will always be true?


Solution

  • I have looked into the linux source code, and have come up with an answer to my own question: there is no race condition, as the signalfd watchers are explicitly notified before the signal is sent, so they will always be notified before the signal is sent (and caught). Specifically, in linux/kernel/signal.c, we see:

    out_set:
        signalfd_notify(t, sig); // <-- signalfd notified
        sigaddset(&pending->signal, sig);
        ...
        complete_signal(sig, t, type); // <-- sends the signal
    

    so it is not possible for sigwait to consume the signal before signalfd is notified about the signal.