1

I have a parent process (client) talking with a child process (server) over Unix Domain sockets (aka IPC Sockets).

The sockets are created using socketpair() and are of type datagram.

I use posix_spawn() to start the child process, and close() the end of the socket pair that I don't need in the parent and child.

On the child I use poll() and recv().

This all works great.

Now, I want the child (server) to be notified when the client closes their end of the socket or when the client terminates.

I was expecting to get a POLLHUP or POLLERR event on close(), but I don’t get anything.

When using lsof -U to list the Unix Domain sockets opened by my 2 process I see that the other end of the socket is none after the client is killed.

This is on macOS if this matters.

What am I missing? How does one get notified when a client close a Unix Domain socket?

1

I've put together a sample application based on your description, with some small differences. First, I use SOCK_STREAM instead of SOCK_DGRAM. This change is what gives me the behavior that you're looking for -- POLLHUP means that the socket is no longer connected; SOCK_DGRAM isn't a connection-oriented socket. Is there a reason that you need to use SOCK_DGRAM?

A second small difference between this example and what you describe is that I use fork() instead of posix_spawn(). That shouldn't affect the behavior in which you're interested; it was just easier for me to write the self-contained example with fork().

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

int main(void)
{
    int sockets[2] = {};

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
        perror("socketpair");
        return 1;
    }

    const pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return 2;
    }

    if (pid > 0) {
        /* parent */
        close(sockets[0]);
        sockets[0] = 0;

        for (int i = 0; i < 3; ++i) {
            write(sockets[1], "hello", sizeof("hello"));
            sleep(2);
        }

        /* sockets[1] will get closed on parent termination */
        return 0;
    }

    /* child */
    close(sockets[1]);
    sockets[1] = 0;

    struct pollfd fds = {
        .fd = sockets[0],
        .events = POLLIN | POLLHUP,
    };

    while (poll(&fds, 1, 10 * 1000) > 0) {
        if (fds.revents & POLLHUP) {
            printf("--- Received hangup\n");
            break;
        }
        if (fds.revents & POLLERR) {
            printf("!!! Received error\n");
            break;
        }
        if (fds.revents & POLLIN) {
            char buffer[32] = {};

            if (recv(sockets[0], buffer, sizeof buffer, 0) < 0) {
                perror("recv");
                return 3;
            }

            printf("--> Received message '%s'\n", buffer);
        }
    }

    /* sockets[0] will get closed on child termination */
    return 0;
}

In this program, the parent writes messages to the child; the child receives those messages and processes them (here, just prints them to standard output). The parent sends 3 messages, then terminates.

When I run this program, I see the behavior that I think you're looking for:

$ ./a.out
--> Received message 'hello'
--> Received message 'hello'
--> Received message 'hello'
--- Received hangup

If you must use SOCK_DGRAM then the call to poll() will eventually timeout (in my example I have a 10-second timeout). You could shut down the client process on that event.

7
  • SOCK_DGRAM is required because it gives me message framing for free. A stream doesn't. But the sample is good. In my case, (on macOS with DGRAM) I never get the POLLHUP, but I do get a POLLIN event on close, and modulo a bug where I wasn't checking the return value of recv properly, this works.
    – Droopycom
    Apr 21 '20 at 4:28
  • Ah, and the other thing is that it works if the client close() it's socket, but not if the client is killed or exit before calling close().
    – Droopycom
    Apr 21 '20 at 5:34
  • Your rationale for using SOCK_DGRAM makes sense. I see similar behavior with my example; I don't think POLLHUP is expected with SOCK_DGRAM as there is no "connection" to the sender. Apr 22 '20 at 1:21
  • There shouldn't be a fundamental difference between close() and application termination. When the application is terminated, its file descriptors are closed implicitly. Apr 22 '20 at 1:23
  • 1
    It also looks like having a timeout and retrying poll() is key. It looks like poll is checking the connection status of the socket when it is called, and will return an POLLIN event right away if the socket was already disconnected before the call to poll, but will not return an event if it is disconnected while waiting. This may be platform specific (macOS in my case)
    – Droopycom
    Apr 27 '20 at 18:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.