del del - 5 months ago 33
Linux Question

Seeing two accept events on epoll

I'm playing around with epoll on Linux for the first time and see some strange behavior. Specificall, when I connect with a client to a socket, I see two events emitted by epoll_wait on the server side. When I call accept on the second attempt I get a "temporary unavailable" error.

Here's the simple client code:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

const char* msg = "friendly ping";

int main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <socket>\n", argv[0]);
exit(-1);
}

int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(-1);
}

struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);

if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("Error connecting");
exit(-1);
}

write(sock, msg, strlen(msg));
close(sock);

return 0;
}


The server code is the following:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

const int kMaxEvents = 100;
const char *kSocketFile = "dummy_socket";

void MakeNonBlocking(int fd) {
int flags, s;

flags = fcntl (fd, F_GETFL, 0);
if (flags == -1) {
perror ("fcntl");
exit(-1);
}

flags |= O_NONBLOCK;
s = fcntl (fd, F_SETFL, flags);
if (s == -1) {
perror ("fcntl");
exit(-1);
}
}

void AcceptConnections(int sock, int epoll_fd) {
struct epoll_event event;
event.data.fd = sock;
event.events = EPOLLIN;

int insock = accept(sock, NULL, NULL);
if (insock < 0) {
perror("Error accepting connection");
exit(-1);
}

MakeNonBlocking(insock);

int s = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, insock, &event);
if (s < 0) {
perror("Epoll error adding accepted connection");
exit(-1);
}
printf("Connection processed.\n");
}

int main(void) {
int sock, efd, n;
struct sockaddr_un addr;
struct epoll_event event;
struct epoll_event *events;
char buf[1024];

if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("Error creating socket.");
exit(-1);
}

addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, kSocketFile, sizeof(addr.sun_path) - 1);
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("Error binding name to socket");
exit(-1);
}

if (listen(sock, SOMAXCONN) < 0) {
perror("Error listening on socket");
exit(-1);
}

MakeNonBlocking(sock);

if ((efd = epoll_create1(0)) < 0) {
perror("Epoll initialization error");
exit(-1);
}

event.data.fd = sock;
event.events = EPOLLIN;
if (epoll_ctl(efd, EPOLL_CTL_ADD, sock, &event) < 0) {
perror("Epoll error adding socket");
exit(-1);
}

events = (struct epoll_event*) calloc(kMaxEvents, sizeof(event));
if (!events) {
perror("Error allocating event buffers");
exit(-1);
}

while(1) {
printf("Calling epoll_wait\n");
if ((n = epoll_wait(efd, events, kMaxEvents, -1)) == -1) {
perror("epoll_wait failure");
exit(-1);
}

for (int i = 0; i < n; ++i) {
printf("Checking event for fd = %d\n", events[i].data.fd);
if (sock == events[i].data.fd) {
AcceptConnections(sock, efd);
continue;
}

int count = read(events[i].data.fd, buf, 100);
if (count == 0) {
close(events[i].data.fd);
}
write(1, buf, count);
}
}

free(events);
close(efd);
close(sock);
unlink(kSocketFile);
return 0;
}


When I run the server and connect to the client I get:

Calling epoll_wait
Checking event for fd = 3
Connection processed.
Calling epoll_wait
Checking event for fd = 3
Error accepting connection: Resource temporarily unavailable


In other words, I see two events on the listening socket. Any ideas?

Answer

To fix, change AcceptConnections to:

void AcceptConnections(int sock, int epoll_fd) {
  struct epoll_event event;
  event.events = EPOLLIN;

  int insock = accept(sock, NULL, NULL);
  if (insock < 0) {
    perror("Error accepting connection");
    exit(-1);
  }

  // This is the important change.
  event.data.fd = insock;

  MakeNonBlocking(insock);

  int s = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, insock, &event);
  if (s < 0) {
    perror("Epoll error adding accepted connection");
    exit(-1);
  }
  printf("Connection processed.\n");
}

epoll is sometimes funny...

The issue is with the way you register the event in AcceptConnections.

epoll accepts two different fd values, one in event.data.fd (user opaque data value) and the other in the beginning of the epoll_ctl function call (the fd controlling the event).

The event.data.fd can be anything, and it's the actual data you read in your event loop...

... in your original code, you set it to the listening socket instead of the client socket.

So, when the client socket has data ready to be read, an event is raised with event.data.fd pointing at the listening socket instead of the client socket.

Since you don't clear the event (for the client socket), it's repeatedly raised with the data you set (the listening socket fd).

Good Luck!