Use Case: Self-Pipe Trick#

Problem#

  • Want to do more in signal handlers

  • Probably not a good idea: cannot do more in signal handlers (see here)

Solution#

  • Defer signal handling to main loop

  • Transport medium: pipe between signal handler context and program context

#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <print>

int sig_channel_read, sig_channel_write;

static void handler(int signal)
{
    ssize_t nwritten = write(sig_channel_write,        // <-- send command to main loop
                             &signal, sizeof(signal));
    if (nwritten == -1)
        _exit(2);                                      // <-- perror (and even exit()) not async-signal-safe
}


int main()
{
    std::println("PID={}", getpid());

    int pipe_ends[2];
    int rv = pipe(pipe_ends);
    if (rv == -1) {
        perror("pipe");
        return 1;
    }
    sig_channel_read = pipe_ends[0];
    sig_channel_write = pipe_ends[1];

    struct sigaction sa = { 0 };
    sa.sa_handler = handler;

    rv = sigaction(SIGTERM, &sa, nullptr);
    if (rv == -1) {
        perror("sigaction(SIGTERM)");
        return 1;
    }
    rv = sigaction(SIGINT, &sa, nullptr);
    if (rv == -1) {
        perror("sigaction(SIGINT)");
        return 1;
    }
    rv = sigaction(SIGUSR1, &sa, nullptr);
    if (rv == -1) {
        perror("sigaction(SIGUSR1)");
        return 1;
    }
    rv = sigaction(SIGUSR2, &sa, nullptr);
    if (rv == -1) {
        perror("sigaction(SIGUSR2)");
        return 1;
    }

    bool quit = false;
    while (!quit) {
        int signal;
        ssize_t nread = read(sig_channel_read,         // <-- read commands from signal context
                             &signal, sizeof(signal));
        if (nread == -1) {
            if (errno == EINTR)                        // <-- restart manually
                continue;
            else {
                perror("read");
                return 1;
            }
        }
        assert(nread==sizeof(signal));

        switch (signal) {                              // <-- interpret commands
            case SIGTERM:
            case SIGINT:
                std::println("terminating");
                quit = true;
                break;
            case SIGUSR1:
                std::println("doing this");
                break;
            case SIGUSR2:
                std::println("doing that");
                break;
        }
    }

    return 0;
}