cyclops
cyclops

Reputation: 51

The select() system call isn't working as expected

I am using select system to wait for input.Also i am doing this in a loop. Here is the code.

int main()
{
    fd_set rfds;
    struct timeval tv;  

    FD_ZERO(&rfds);
    FD_SET(0,&rfds);

    tv.tv_sec = 5;
        tv.tv_usec = 0;

    while(1)
    {
        select(1,&rfds,NULL,NULL,&tv);
        if(FD_ISSET(0,&rfds))
        {
            write(STDOUT_FILENO,"yes",3);
            FD_CLR(0,&rfds);    
        }

        tv.tv_sec = 5;
            tv.tv_usec = 0; 
    }   
    return 0;

}

Now the problem is that the select call is working fine only for the first time.If I give input within the first 5 sec I get yes as output but then in the following iterations fd(0) remains unset irrespective of whether I provide any input or not.Any idea how I can solve this problem.

Upvotes: 1

Views: 3428

Answers (3)

mehmet riza oz
mehmet riza oz

Reputation: 559

Once getting a data from stdin the code clears read file descriptor set(rfds) using FD_CLR(0,&rfds) command so in the next loop there your read file descriptor is empty

Upvotes: 0

On some implementations select(2) is allowed to modify the file descriptor sets and the timeout. So you should set these inside the loop just before the select

while(1) {
  FD_ZERO(&rfds);
  FD_SET(0,&rfds);
  tv.tv_sec = 5;
  tv.tv_usec = 0;
  int ns = select(1,&rfds,NULL,NULL,&tv);
  if (ns < 0 && errno == EINTR) continue;
  else if (ns < 0) { perror("select"); exit(EXIT_FAILURE); };

You need to keep the result ns of select, and you need to handle error cases. You probably should inspect rfds and tv after your select -when it is successful (ns>0)...

As I commented, you should use poll(2) instead of select (since poll is more C10K problem friendly, and since the size of your system's fd_set is compile-time limiting the highest file descriptor)

Notice that if your stdin is a tty things are quite complex (since usually tty-s are kernel buffered, see the tty demystified page).

Upvotes: 1

Wintermute
Wintermute

Reputation: 44063

There are two problems with your code.

Problem one: select on empty file descriptor sets

The first is that select modifies the file descriptor sets it is given -- after select returns, they contain the file descriptors that are ready for I/O. This means that if the timeout passes without any input on stdin, rfds will be empty, and the next call to select will wait for input on an empty file descriptor set -- where it will never find any.

Input on stdin would keep STDIN_FILENO (which is 0) in the set, but since in the event that it turns up you call

FD_CLR(0,&rfds);

to remove stdin from the set, select will wait on an empty fd set in that case as well. I know why you put that there, though, and it is related to the second problem (see below). The fix for this first problem, in any case, is to put stdin back into the fd set before you call select again:

FD_SET(0, &rfds);

Problem two: Input sitting in stdin for ever and ever

The second problem is that while your program waits for input on stdin, it never consumes any. This means that if you fix the file descriptor set before every call to select with FD_SET(0, &rfds);, your program will end up printing "yes" over and over in an infinite loop.

This is because once input is waiting to be consumed in stdin, calling select on that file descriptor will result in select checking if there's input waiting, recognizing that yes, there is, telling you about that fact, whereupon you don't do anything with it and just ask select to check if it is still there. Which it is, no matter how often you check.

I don't know how exactly you want to consume the input, so this next bit is guesswork. I'm assuming that you want the program to write "yes" if the user entered something. When a user entered something is not always clearly defined, but a common interpretation is to say that user input is typically line-based -- the user expects things to happen once he pressed return. One sane method, then, would be to discard until the next newline when data appeared, so that every press on the return button yields a "yes". That might look like this:

while ((c = getchar()) != EOF) && c != '\n'); // discard until end-of-line

with int c;.

Putting it together

In summary, this might do what you want:

#include <sys/select.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
  fd_set rfds;
  struct timeval tv;
  int c;

  FD_ZERO(&rfds);
  FD_SET(0,&rfds);

  tv.tv_sec = 5;
  tv.tv_usec = 0;

  while(1)
  {
    select(1,&rfds,NULL,NULL,&tv);
    if(FD_ISSET(0, &rfds))
    {
      puts("yes");
      while((c = getchar()) != EOF && c != '\n'); // consume input
    } else {
      puts("no");
      FD_SET(0, &rfds); // place stdin back in the fd set
    }

    tv.tv_sec = 5;
    tv.tv_usec = 0; 
  }

  return 0;
}

Upvotes: 3

Related Questions