select() bug

Paul Marquis
2000-11-02T22:12:04Z

[-- Attachment #1: Type: text/plain, Size: 890 bytes --]

I've uncovered a bug in select() when checking if it's okay to write
on a pipe. It will report "false negatives" if there is any unread
data already on the pipe, even a single byte. As soon as the pipe
gets flushed, select() does the right thing.

Normally, I wouldn't think this such a big deal, except that Apache
uses the writability of s pipe to determine if any of its children
that are log file handlers are dead. If select() reports it can't
write immediately, Apache terminates and restarts the child process,
creating unnecessary load on the system.

The bug exists in the 2.2.x kernels up to and including 2.2.17 that
I've tried and I don't know it extends beyond pipes. I've attached
sample code to demonstrate the problem, which works correctly on BSD
and Solaris.

Is this a know bug?

--
Paul Marquis
pmarquis@iname.com

If it's tourist season, why can't we shoot them?

[-- Attachment #2: simple_pipe_test.c --]
[-- Type: application/octet-stream, Size: 2882 bytes --]

#include <sys/time.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
int fd[2];
int i;
char buf[2];
fd_set write_fds;
struct timeval tv;
ssize_t n;
int status;

/* initialize */
buf[0] = 'h';
buf[1] = 'i';

do
{
/* create pipe */
if (0 > pipe(fd))
{
printf("pipe error - %s\n", strerror(errno));
break;
}

/*
* loop four times
* select for write, then write one byte
* after the second write, do a read
* odd selects succeed, even fail
*/
for (i = 0; i < 4; i++)
{
printf("iteration %d\n", i + 1);

status = 0;

/* do select */
FD_ZERO(&write_fds);
FD_SET(fd[1], &write_fds);
tv.tv_sec = 0;
tv.tv_usec = 0;
switch (select(fd[1] + 1, NULL, &write_fds, NULL, &tv))
{
case -1:
/* should probably check for EINTR and/or EWOULDBLOCK */
printf("select error - %s\n", strerror(errno));
break;
case 0:
/* no I/O */
puts("select returned 0 -- pipe not writable");
break;
default:
/* make sure our fd is writable */
if (FD_ISSET(fd[1], &write_fds))
{
puts("select says pipe is writable");
status = 1;
}
else
puts("select says pipe is not writable");
break;
}

/* do write */
n = write(fd[1], buf + (i % 2), 1);
switch (n)
{
case -1:
/* should probably check for EINTR and/or EWOULDBLOCK */
printf("write error - %s\n", strerror(errno));
break;
case 0:
/* no I/O */
puts("write returned 0 -- pipe closed");
break;
default:
printf("write succeeded - %d bytes written\n", n);
if (0 == status)
puts("select bug");
break;
}

/* do read */
if (0 == ((i + 1) % 2))
{
n = read(fd[0], buf, sizeof(buf));
switch (n)
{
case -1:
/* should probably check for EINTR and/or EWOULDBLOCK */
printf("write error - %s\n", strerror(errno));
break;
case 0:
/* no I/O */
puts("read returned 0 -- pipe closed");
break;
default:
printf("read succeeded - %d bytes read\n", n);
break;
}
}
}
}
while (0);

exit(0);
}