Skip site navigation (1)Skip section navigation (2)
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>