Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

select() returns invalid argument

Tags:

c

unix

select

pipe

I am successfully reading from a pipe from another thread, and printing the output (in an ncurses window as it happens).

I need to do this one character at a time, for various reasons, and I'm using a select() on the FD for the read end of the pipe, along with a few other FDs (like stdin).

My idea is to attempt to read from the pipe only when it is imminently ready to be read, in preference to handling any input. This seems to be working - at least to start. select() sets up the fd_set and if FD_ISSET I do my read() of 1 byte from the FD. But select() says yes one time too many, and the read() blocks.

So my question is this - why would select() report that a fd is ready for reading, if a subsequent read() blocks?

(approximately) This same code worked fine when the other end of the pipe was connected to a forked process, if that helps.

I can post the code on request, but it's bog standard. Set up a fd_set, copy it, select the copy, if the FD is set call a function which reads a byte from the same FD... otherwise revert the fd_set copy

EDIT: by request, here's the code:

Setting up my fd_set:

fd_set fds;
FD_ZERO(&fds); 
FD_SET(interp_output[0], &fds);
FD_SET(STDIN_FILENO, &fds);
struct timeval timeout, tvcopy; timeout.tv_sec=1;
int maxfd=interp_output[0]+1; //always >stdin+1
fd_set read_fds;
FD_COPY(&fds, &read_fds);

In a loop:

if (select(maxfd, &read_fds, NULL, NULL, &timeout)==-1) {perror("couldn't select"); return;}
if (FD_ISSET(interp_output[0], &read_fds)) {
    handle_interp_out();
} else if (FD_ISSET(STDIN_FILENO, &read_fds)) {
//waddstr(cmdwin, "stdin!"); wrefresh(cmdwin);
    handle_input();
}

FDCOPY(&fds, &read_fds);

handle_interp_out():

void handle_interp_out() {
    int ch;
    read(interp_output[0], &ch, 1);
    if (ch>0) {
            if (ch=='\n') { if (cmd_curline>=cmdheight) cmdscroll(); wmove(cmdwin, ++cmd_curline, 1); }
            else waddch(cmdwin, ch);
            wrefresh(cmdwin);
    }
}

EDIT 2: The write code is just a fprintf on a FILE* opened with fdopen(interp_output[1], "w") - this is in a different thread. All I'm getting to is my "prompt> " - it prints all that properly, but does one more iteration that it shouldn't. I've turned off buffering, which was giving me other problems.

EDIT 3: This has become a problem with my invocation of select(). It appears that, right away, it's returning -1 and errno's being set to 'invalid argument'. The read() doesn't know that and just keeps going. What could be wrong with my select()? I've updated the code and changed the title to more accurately reflect the problem...

EDIT 4: So now I'm thoroughly confused. The timeout value of .tv_sec=1 was no good, somehow. By getting rid of it, the code works just fine. If anybody has any theories, I'm all ears. I would just leave it at NULL, except this thread needs to periodically do updates.

like image 553
Robert Avatar asked Dec 06 '10 08:12

Robert


3 Answers

To absolutely guarantee that the read will never block, you must set O_NONBLOCK on the fd.

Your select error is almost certainly caused because you aren't setting the entire time struct. You're only setting the seconds. The other field will contain garbage data picked up from the stack.

Use struct initialization. That will guarantee the other fields are set to 0.

It would look like this:

struct timeval timeout = {1, 0};

Also, in your select loop you should be aware that Linux will write the time remaining into the timeout value. That means that it will not be 1 second the next time through the loop unless you reset the value to 1 second.

like image 134
Zan Lynx Avatar answered Oct 31 '22 19:10

Zan Lynx


According to the manpage:

On error, -1 is returned, and errno is set appropriately; the sets and timeout become undefined, so do not rely on their contents after an error.

You are not checking the return code from select().

The most likely explanation is that select() is being interrupted (errno=EINTR) and so returning an error, and the FD bit is still set in the "read" fd_set giving you the behaviour you are seeing.

Incidentally it is a very bad idea to name variables after standard/system/common functions. "read_fds" would be a MUCH better name than "read".

like image 3
AlastairG Avatar answered Oct 31 '22 20:10

AlastairG


It's correct. See the select() manpage in Linux for example: http://linux.die.net/man/2/select

"Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks"

The only solution is to use NON-BLOCKING socket.

like image 2
ama Avatar answered Oct 31 '22 19:10

ama