Date: Sat, 15 Jun 1996 23:04:02 +1000 From: Bruce Evans <bde@zeta.org.au> To: cimaxp1!jb@werple.net.au, hackers@FreeBSD.org Cc: jb@cimlogic.com.au Subject: Re: Nonblocking writes to pipes hang Message-ID: <199606151304.XAA12653@godzilla.zeta.org.au>
next in thread | raw e-mail | index | archive | help
>We're having problems with non-blocking writes to pipes under >FreeBSD 2.1R and 2.2-current (supped middle of last week). The >test program (below) gives the following results: >... >The program on either FreeBSD 2.1R or 2.2-current just hangs. >Only a kill -9 will interrupt it. >Can anyone explain what is going on? Writes of >= 8192 bytes are just broken. pipe_direct_write() is called in this case and it never checks the PIPE_NBIO flag. Pipes of size < 8191 probably work but are considerably less efficient. On a P133 with I586_FAST_BCOPY and a profiling kernel, I get about 30MB/sec through pipes with block sizes in the range 4K to 8K-1 and about 54M/sec for a block size of 8K. The max is about 64M/sec for a block size of 16K. Non-profiling kernels are slightly faster (up to 68MB/sec). The maximum speed of bcopy on this system is about 77MB/sec. My pipe throughput test program shows another interesting pipe bug. When WORK_AROUND_FREEBSD_BUGS is not defined, the reader sometimes hangs waiting for EOF even though the writer has closed the pipe (or has exited). This is caused by last-close semantics being broken as designed. The fileops close function only gets called when both the reader and the writer have closed the pipe. Thus if the reader is waiting for input and the writer dies, the pipe code doesn't get notified and the reader continues waiting for input that can never arrive. For named pipes, input can arrive, but reads still block for too long. POSIX.1 requires that reads unblock when the pipe is closed by all processes that had it open for writing. FreeBSD's bidirectional pipes seem to fix the problem for the wrong reason - it is impossible to close a read/write descriptor while you're reading from it, so it is impossible for all writers to close a pipe that you're reading from. Bruce #if 0 #define DEBUG #endif #define WORK_AROUND_FREEBSD_BUGS #include <sys/types.h> #include <sys/time.h> #include <sys/wait.h> #include <errno.h> #include <limits.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> static sig_atomic_t caught; #ifdef DEBUG static pid_t child; #endif static char *progname; static void catch(int sig) { #ifdef DEBUG fprintf(stderr, "pid %ld caught signal %d\n", child, sig); #endif caught = 1; } static void usage(void) { fprintf(stderr, "usage: %s [blocksize [timefor]]\n", progname); exit(1); } int main(int argc, char **argv) { size_t blocksize; void *buf; #ifndef DEBUG pid_t child; #endif char *ep; int errs; int fd[2]; ssize_t nread; ssize_t nwritten; struct timeval start, finish; int status; unsigned timefor; long long totread; double tottime; long long totwritten; unsigned long ulblocksize; unsigned long ultimefor; progname = argv[0]; if (argc < 1 || argc >= 4) usage(); blocksize = 4096; if (argc >= 2) { ulblocksize = strtoul(argv[1], &ep, 0); if (ulblocksize == 0 || ulblocksize > INT_MAX) { fprintf(stderr, "%s: invalid blocksize %lu\n", progname, ulblocksize); } blocksize = ulblocksize; } timefor = 1; if (argc >= 3) { ultimefor = strtoul(argv[2], &ep, 0); if (ultimefor == 0 || ultimefor > INT_MAX) { fprintf(stderr, "%s: invalid timefor %lu\n", progname, ultimefor); } timefor = ultimefor; } buf = malloc(blocksize); if (buf == NULL) { perror("malloc"); exit(1); } memset(buf, 0, blocksize); if (signal(SIGALRM, catch) == SIG_ERR) { perror("signal"); exit(1); } if (pipe(fd) != 0) { perror("pipe"); exit(1); } #ifdef WORK_AROUND_FREEBSD_BUGS if (siginterrupt(SIGALRM, 1) != 0) { perror("signal"); exit(1); } #endif if (gettimeofday(&start, (struct timezone *) NULL) != 0) { perror("gettimeofday"); exit(1); } switch (child = fork()) { case -1: perror("fork"); exit(1); case 0: if (alarm(timefor) != 0) { perror("child alarm"); exit(1); } totread = 0; while (!caught) { nread = read(fd[0], buf, blocksize); if (nread <= 0) { if (nread < 0 && (nread != -1 || !caught || errno != EINTR)) { perror("read"); exit(1); } #ifdef DEBUG if (nread == -1 && errno == EINTR) fprintf(stderr, "%s: child EINTR\n", progname); #endif break; } totread += nread; } if (gettimeofday(&finish, (struct timezone *) NULL) != 0) { perror("gettimeofday"); exit(1); } tottime = finish.tv_sec - start.tv_sec + (finish.tv_usec - start.tv_usec) * 1e-6; fprintf(stdout, "blocksize %u : read %qd bytes in %.6f seconds\n", blocksize, totread, tottime); #ifdef DEBUG fprintf(stderr, "%s: child exiting\n", progname); #endif exit(0); default: errs = 0; if (alarm(timefor) != 0) { perror("parent alarm"); ++errs; goto out; } totwritten= 0; while (!caught) { nwritten = write(fd[1], buf, blocksize); if (nwritten <= 0) { if (nwritten != -1 || !caught || errno != EINTR) { fprintf(stderr, "%s: wrote %d\n", progname, nwritten); perror("write"); ++errs; } break; } totwritten += nwritten; } out: if (close(fd[0]) != 0) { perror("close"); ++errs; } if (close(fd[1]) != 0) { perror("close"); ++errs; } #ifdef DEBUG fprintf(stderr, "%s: parent waiting\n", progname); #endif if (waitpid(child, &status, 0) != child) { perror("wait"); ++errs; } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "%s: bad exit status %x\n", progname, status); ++errs; } #ifdef DEBUG fprintf(stderr, "totwritten = %qd\n", totwritten); #endif exit(errs == 0 ? 0 : 1); } }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199606151304.XAA12653>