c++ubuntusignalsunix-socket

No SIGHUP on default Ubuntu by closing socketpair


The following code should call the signal handler once the pipes are closed by the parent. The signal handler will be sporned by the child, cause it waits one second. This wait cycle should be interrupted by the signal handler.

Only output:

n=10, 'Hello Worl'  // perfect line ;), but the next line is missing.
// "signal received: ...  MISSING

How ever, the signal handler is not invoked. Can someone tell, why?

hint: As no Signal is processed, the child is left behind. Terminate it manually.

#include <csignal>
#include <cstdio>

#include <sys/socket.h>
#include <unistd.h>

void child(int socket) {
    while (true) {
        char buf[100];
        int n = read(socket, buf, 10);
        printf("n=%d, '%s'\n", n, buf);
        sleep(1);
    }
}

void parent(int socket) {
    char buf[] = "Hello World\n";
    write(socket, buf, sizeof(buf));

    close(socket);
}

int main() {
    int socket[2];
    socketpair(AF_LOCAL, SOCK_DGRAM, 0, socket);

    signal(SIGHUP, [](int sig) {
        printf("signal received: %d\n", sig);
    });

    switch (fork()) {
        case 0:
            close(socket[1]);
            child(socket[0]);
            break;

        default:
            close(socket[0]);
            parent(socket[1]);
            break;

        case -1:
            return 1;
    }

    return 0;
}

Solution

  • the intention is, that if one side dies, the socketpair in term is destroyed and this should cause some kind of signal which should lead to a collapse of all started processes

    Well, in that case a datagram socket is just about the only thing you cannot use.

    If you want to continue using file descriptors for this and/or stay fully POSIX-compatible, a stream socket or pipe is what you can use.

    #include <poll.h>
    
    #include <sys/types.h>
    #include <sys/socket.h>
    // using socketpair
    
    #include <unistd.h>
    // using fork, sleep, close
    
    #include <cstdio>
    // using std::puts
    
    
    int main()
    {
      int sockets[2];
      socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
      if(fork()) { // parent
        close(sockets[1]);
        sleep(1);
        std::puts("Parent exit");
      }
      else { // child
        close(sockets[0]);
        pollfd poll_fd = { sockets[1], POLLIN, 0 };
        poll(&poll_fd, 1, 2000 /*timeout*/);
        if(poll_fd.revents & POLLIN)
          std::puts("Parent died before child");
        else
          std::puts("Child exit before parent");
      }
    }
    

    Personally, if I don't have need for a socket, I just use a pipe, keeping the write-side open on the parent and the read-side in the child. When the write-side closes (or on a stream socket if either side closes), reading will unblock (indicating EOF).

    This approach is most useful if you have some sort of event loop since you can usually just plug it into those like I did here with the poll. If you don't and you want a signal instead, you may use SIGIO and interrupt-driven input but I find this rather tedious, especially for larger process trees.

    If you are fine with a Linux-specific solution, prctl(PR_SET_PDEATHSIG, SIGHUP) is what you want.

    #include <sys/types.h>
    #include <unistd.h>
    // using getpid, getppid, pause, sleep
    #include <sys/prctl.h>
    
    #include <csignal>
    #include <cstdio>
    // using std::puts. std::printf
    #include <cstring>
    // using std::strlen
    
    
    int main()
    {
      pid_t parent_id = getpid();
      if(fork()) { // parent
        sleep(1);
        std::puts("Parent exit");
      }
      else { // child
        std::signal(SIGHUP, [](int sig) {
          const char* msg = "Child received SIGHUP\n";
          // puts or printf are not signal-safe
          write(1 /*stdout*/, msg, std::strlen(msg));
          _exit(1); // std::exit is not signal-safe!
        });
        prctl(PR_SET_PDEATHSIG, SIGHUP);
        if(getppid() != parent_id) {
          std::puts("Parent died during child setup");
          return 0;
        }
        while(true)
          pause();
      }
    }
    

    Note this warning from the man-page:

    the "parent" in this case is considered to be the thread that created this process. In other words, the signal will be sent when that thread terminates (via, for example, pthread_exit(3)), rather than after all of the threads in the parent process terminate.

    BTW: Your use of printf in a signal handler was erroneous. None of the C stdio functions are async-signal-safe.