Skip to content
Advertisement

Unexpected behaviour of SO_SNDTIMEO and SO_RCVTIMEO

I’m trying to set the timeout for the blocking TCP socket on Linux using setsockopt with SO_SNDTIMEO and SO_RCVTIMEO. But for some reason I get a lock while waiting on recv call.

Consider the minimal example. I’ve shortened it a bit for readability, the full code is available in this gist. I create a server socket and set the timeouts using setsockopt. Next, the server exchanges messages with the client. After a while, the client interrupts data exchange and closes the socket. But the server still waiting in blocking recv. However, I expect it to abort recv when the timeout expires.

Network functions used by the client and server:

int set_timeout(int fd, int sec) {
  struct timeval timeout;
  timeout.tv_sec = sec;
  timeout.tv_usec = 0;

  if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
                 sizeof(timeout)) < 0) {
    return -1;
  }
  if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
                 sizeof(timeout)) < 0) {
    return -1;
  }

  return 0;
}

int blocking_recv(int sock, char *data, size_t size) {
  size_t remain_data = size;
  ssize_t recv_nb;
  int offset = 0;

  while ((remain_data > 0) &&
         ((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) {
    remain_data -= recv_nb;
    offset += recv_nb;
  }
  if (recv_nb == -1 || remain_data > 0) {
    return -1;
  }

  return 0;
}

int blocking_send(int sock, char *buf, size_t size) {
  size_t total = 0;
  int remain = size;
  int n;

  while (total < size) {
    n = send(sock, buf + total, remain, 0);
    if (n == -1) {
      break;
    }
    total += n;
    remain -= n;
  }

  return (total == size) ? 0 : -1;
}

Server:

while (1) {
  struct sockaddr_in caddr;
  size_t len = sizeof(caddr);
  if ((client_fd = accept(server_fd, (struct sockaddr *)&caddr, (socklen_t *)&len)) < 0) {
    continue;
  }

  // Set 3 seconds timeout using SO_{SND,RCV}TIMEO
  if (set_timeout(client_fd, 3) != 0) {
    continue;
  }

  // Send and receive data in the loop. We assume that the client is stuck,
  // we'll break the connection using a timeout.
  while (1) {
    char in_data[data_size];
    char *out_data = "test";
    if (blocking_recv(client_fd, in_data, data_size) != 0)
      break;
    if (blocking_send(client_fd, out_data, data_size) != 0)
      break;
  }
}

Client:

int attempts_left = 5;
while (--attempts_left > 0) {
  char in_data[6];
  char *out_data = "test";
  if (blocking_send(fd, out_data, 6) != 0)
    break;
  if (blocking_recv(fd, in_data, 6) != 0)
    break;
  sleep(1);
}

close(fd);

What could be wrong?

Is it possible to implement the timeout for recv in this way, without using select, poll and signals?

Thanks!

Advertisement

Answer

You are looping your reading:

while ((remain_data > 0) &&
         ((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) {
    remain_data -= recv_nb;
    offset += recv_nb;
  }

yet read from man read returns:

 If no messages are available to be received and the peer has
   performed an orderly shutdown, recv() shall return 0.

So you are just looping endlessly.

You should correctly handle errno in your program. when SNDTIMEO stuff timeouts, you get: the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) ....

Still EAGAIN can be returned when signal interrupts, so just the usual select() or poll() for 3 seconds would be probably simpler. If not, measure time yourself anyway and set the timeout to the max timeout you want and measure how much time has passed. Along:

timeout = now + 3 seconds.
// check timeout yourself
while (timeout_not_expired(&timeout)) {
   // set timeout for the __next__ recv operation
   set_recv_timeout(timeout_to_expire(&timeout));
   ret = recv();
   if (ret == -1 && errno == EAGAIN) {
      continue;
   }
   if (ret == -1) {
        /* handle errror */
   }
   if (ret == 0) {
      /* closed */
   }
   /* actually received stuff */
}

Use tools like strace and a debugger to debug your programs.

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement