The Linux Page

select() returns immediately instead of waiting for timeout

The select() Unix command allows you to wait on an event on a set of file descriptions, in most cases, sockets.

The select() function accepts the following parameters:

  • Largest file descriptor + 1
  • An fd_set of file descriptors to listen to for read
  • An fd_set of file descriptors to listen to for write
  • An fd_set of file descriptors to listen to for special events (i.e. closed on the other end)
  • A timeout structure

The fd_set pointers are all optional. Actually, if all are set to NULL, select() waits for the specified timeout and then returns. Similarly, the timeout structure is optional, and if not present, select() blocks until something happens to one of those file descriptors.

So... up to here, not much of a problem, right?

Say you have a socket that you want to wait on. You are probably going to either be a receiver or a sender on that socket. So you want to either wait for data to have arrived (read fd_set) or for space in the buffer to write data (write fd_set). Therefore, in case of a socket, you might as well waits on both, read and write.

The problem is that if you put the file descriptor in both fd_set, read and write, you will wake up immediately if you have nothing to write to that file descriptor. If the only reason why you'd have to write something is because you receive something, then you generally do not need to wait for the write signal, only the read. If what you'd have to write may be bigger than the buffers offered by the system, then you want to look into using the wait on write but add that wait only if you still have data to write to the output. Otherwise, do not wait on the file descriptor write because there will be permanent room in its buffer and the select() will always return immediately and you'll end up with a tight loop.

So the idea is a forever loop that would look like this:

fd_set read_set;
fd_set write_set;
int r;
struct timeval timeout;
FD_ZERO(&read_set);
FD_SET(file, &read_set);
FD_ZERO(&write_set);
if(my_output_buffer != NULL)
{
  FD_SET(file, &write_set);
}
timeout.tv_sec = 300; // 5 minutes
timeout.tv_usec = 0;
r = select(file + 1, &read_set, &write_set, &read_set, &timeout);
if(r == -1)
{
  // handle error
  printf("select error!\n");
}
else if(r == 0)
{
  // IDLE ... do some other work
  printf("select timed out (5 min. passed)\n");
}
else
{
  if(FD_ISSET(file, &fd_read))
  {
    // you can read data from that file descriptor
  }
  if(FD_ISSET(file, &fd_write))
  {
    // you can write data to that file descriptor
  }
}

Note that for the read and write to not block you may have to change the file descriptor to a NONBLOCK stream. Otherwise the read and/or write may block your process and the select() because less useful. Note that select() also works on sockets that use sendmsg() and recvmsg() (so called UDP sockets.)

The NONBLOCK, however, can be much harder to handle (especially if a server may receive a message such as a QUIT and quit immediately before all the pipes were read on the other side leaving the other processes without all the data they were expected to receive.)