From owner-freebsd-isp Tue Aug 5 20:41:08 1997 Return-Path: Received: (from root@localhost) by hub.freebsd.org (8.8.5/8.8.5) id UAA25635 for isp-outgoing; Tue, 5 Aug 1997 20:41:08 -0700 (PDT) Received: from jsbach.n2.net (jsbach.n2.net [207.113.132.22]) by hub.freebsd.org (8.8.5/8.8.5) with ESMTP id UAA25610 for ; Tue, 5 Aug 1997 20:41:05 -0700 (PDT) Received: (from fpm@localhost) by jsbach.n2.net (8.8.5/8.6.12) id UAA23383; Tue, 5 Aug 1997 20:39:48 -0700 (PDT) Message-ID: <19970805203947.52488@jsbach.n2.net> Date: Tue, 5 Aug 1997 20:39:47 -0700 From: Frank MacLachlan To: Matt Eagleson Cc: isp@FreeBSD.ORG, fpm@n2.net Subject: Re: INN Problem References: <3.0.3.32.19970805171509.02f92a94@deepwell.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Mailer: Mutt 0.67 In-Reply-To: <3.0.3.32.19970805171509.02f92a94@deepwell.com>; from Matt Eagleson on Tue, Aug 05, 1997 at 05:15:09PM -0700 Sender: owner-freebsd-isp@FreeBSD.ORG X-Loop: FreeBSD.org Precedence: bulk On Tue, Aug 05, 1997 at 05:15:09PM -0700, Matt Eagleson wrote: > This entry appears in my news.err > about every 2 minutes: > > Aug 5 17:01:00 news innd: overview!:21:proc:2148 cant write Resource > temporarily unavailable > Aug 5 17:01:00 news innd: overview!:21:proc:2148 blocked sleeping 120 > > I know others have had this problem too. > Doe anyone know what is causing this? > > Any assistance would be greatly appreciated. > > Regards, > Matt I had the same problem. In my case, the errors began when expireover was running during an expire. Overchan blocked waiting on locked overview files and fell behind INN. Once more than 65536 bytes accumulated for overchan, INN could *not* write any data to the pipe to overchan w/o blocking (INN insists upon writing all of the data for a channel in a single write operation and, under FreeBSD, would get an EAGAIN error). INN then blocked sleeping for 120 seconds on the channel and retried the write. During the sleep interval, more data accumulated and the write operation failed again. Any attempt to flush the channel also failed. Eventually INN dumped the buffered data to an OVERVIEW! file. I modified INN to retry blocked writes in smaller chunks and reduced the sleep interval from 120 seconds to 15 seconds. I still saw the error messages, but INN could usually write the data to overchan within 2 or 3 sleep intervals. I wasn't happy w/ the above strategy, so I wrote the following program which dynamically buffers data between INN and overchan. I haven't seen any 'cant write Resource temporarily unavailable' errors since I started using it nor have I seen any OVERVIEW! dump files. -- Frank MacLachlan (fpm@n2.net) N2 Networking, San Diego, CA -------------------CUT HERE------------------------ /* ** ** Name: ** chan_buf ** ** ** Description: ** Provides front-end buffering for inn channel fed programs. ** It can, for example, interpolate a buffer process between inn ** and overchan. I use the following entry in my newsfeeds file ** to invoke this program and overchan: ** ** overview!\ ** :*,!junk,!control*\ ** :Tc,WO\ ** :/news/bin/chan_buf /news/bin/overchan ** ** This results in the following situation: ** ** +------+ +----------+ +----------+ ** | innd |--------------->| chan_buf |-------------->| overchan | ** +------+ pipe +----------+ pipe +----------+ ** ** Chan_buf reads data coming from innd into a dynamic buffer pool ** and writes data to overchan when the write would not be blocked. ** When overchan falls behind, chan_buf allocates more buffers to ** hold incoming data. As overchan catches up, unused buffers are ** freed. This minimizes the memory size of the chan_buf process. ** ** It has been successfully used with FreeBSD 2.2-STABLE and INN ** 1.5.1. I haven't tried it w/ other operating systems or diff- ** erent versions of INN. ** ** It should linked against INN's libinn.a to include that library's ** SetNonBlocking function. ** ** ** Usage: ** ** chan_buf progname [args] ** ** where progname is the pathname of the program to execute and ** args are the arguments to pass to progname. ** ** ** Written by: ** Frank MacLachlan (fpm@n2.net) ** 20-Jul-97 */ #include #include #include #include #include #include #include #include #include #include #define MAXLINE 256 #define TRUE 1 #define FALSE 0 #define BUFLEN 8192 /* buffer length (must be power of 2) */ #define BUFMAX (BUFLEN-1) /* max buffer index (0..BUFMAX) */ #define BUFMSK (BUFLEN-1) /* mask used when incrementing buffer ptrs */ /* ** Definition of a buffer: */ typedef struct buf { struct buf *next; /* link to next element */ int curlen; /* current data length (0..BUFLEN) */ int head; /* current buffer head (0..BUFMAX) */ int tail; /* current buffer tail (0..BUFMAX) */ char buf[BUFLEN]; /* data buffer */ } BUF; /* ** Definition of a buffer list: */ typedef struct { BUF *first; /* pointer to first in list */ BUF *last; /* pointer to last in list */ int nbufs; /* number of buffers in list */ } BUFL; /* ** Global data: */ char *MyName; /* name of this program */ BUFL Bufs; /* active buffers list */ /* ** Function prototypes: */ void BufCopy(int RdFD, int WrtFD); BUF *GetNewBuf(void); BUF *RmFromBL(BUFL *blp); void AddToBL(BUFL *blp, BUF *p); int SetNonBlocking(int fd, int flag); void ErrRet(const char *fmt, ...); void ErrSys(const char *fmt, ...); void ErrQuit(const char *fmt, ...); void ErrDoit(int errnoflag, const char *fmt, va_list ap); /* ** Create a pipe, fork w/ child exec'ing program given as parameter. ** Parent writes to pipe, child reads stdin from pipe. Parent becomes ** a buffer process which attempts to promptly read any data present on ** stdin. */ int main(int argc, char *argv[]) { int fd[2]; pid_t pid; char *p; /* Set up MyName for error messages */ if ((p = strrchr(argv[0], '/')) != NULL) MyName = p + 1; else MyName = argv[0]; if (argc < 2) ErrQuit("Usage: %s pathname [args]", MyName); if (pipe(fd) < 0) ErrSys("pipe failed"); if ((pid = fork()) < 0) ErrSys("fork failed"); if (pid > 0) { /* * Parent: copy data from STDIN_FILENO to write end of pipe. */ close(fd[0]); /* close read end of pipe */ BufCopy(STDIN_FILENO, fd[1]); close(fd[1]); /* force EOF for reader */ /* wait for child to terminate */ if (waitpid(pid, NULL, 0) < 0) ErrSys("waitpid error"); exit(0); } else { /* * Child: exec the program we pipe to. */ close(fd[1]); /* close write end of pipe */ /* * Connect read end of child's pipe to stdin. */ if (fd[0] != STDIN_FILENO) { if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) ErrSys("dup2 failed"); close(fd[0]); } if (execv(argv[1], &argv[1]) < 0) ErrSys("execv failed for %s", argv[1]); close(STDIN_FILENO); exit(1); } } /* ** Read any available data from RdFD into buffer, write data to ** WrtFD when possible. */ void BufCopy(int RdFD, int WrtFD) { int avail, n; int ReadEOF = FALSE; /* TRUE if read ret'd EOF */ int MaxFD; int iovcnt; BUF *p; fd_set RdSet, WrtSet; struct iovec iovecs[2]; /* configure output file for non-blocking writes */ if (SetNonBlocking(WrtFD, TRUE) < 0) ErrSys("SetNonBlocking TRUE failed"); Bufs.first = NULL; Bufs.nbufs = 0; /* Allocate a single buffer at first */ AddToBL(&Bufs, GetNewBuf()); FD_ZERO(&RdSet); FD_ZERO(&WrtSet); MaxFD = (WrtFD >= RdFD) ? WrtFD : RdFD; /* * Loop until we see EOF from the input and we've drained * our buffers. */ while (! ReadEOF || Bufs.first->curlen > 0) { /* don't check for read data if we read EOF */ if (! ReadEOF) FD_SET(RdFD, &RdSet); /* check for write ready only if we have buffered data */ if (Bufs.first->curlen > 0) FD_SET(WrtFD, &WrtSet); else FD_CLR(WrtFD, &WrtSet); if (select(MaxFD+1, &RdSet, &WrtSet, NULL, NULL) < 0) ErrSys("select error"); if (FD_ISSET(RdFD, &RdSet)) { /* * Read data available. See how much space we have * in the buffer. If no space, allocate a new buffer. */ if ((avail = BUFLEN - Bufs.last->curlen) <= 0) { AddToBL(&Bufs, GetNewBuf()); avail = BUFLEN; } n = BUFLEN - Bufs.last->tail; if (avail <= n) iovcnt = 1; else { iovecs[1].iov_len = avail - n; iovecs[1].iov_base = Bufs.last->buf; iovcnt = 2; avail = n; } iovecs[0].iov_len = avail; iovecs[0].iov_base = Bufs.last->buf + Bufs.last->tail; if ((n = readv(RdFD, iovecs, iovcnt)) <= 0) { /* * Error or EOF. Set ReadEOF to TRUE to stop * further reads from input. */ if (n < 0) ErrRet("read error"); ReadEOF = TRUE; FD_CLR(RdFD, &RdSet); } else { /* * Got some data. Update buffer pointer, count. */ Bufs.last->tail += n; Bufs.last->tail &= BUFMSK; Bufs.last->curlen += n; } } if (FD_ISSET(WrtFD, &WrtSet)) { /* * We have some data and can write w/o blocking. */ avail = Bufs.first->curlen; n = BUFLEN - Bufs.first->head; if (avail <= n) iovcnt = 1; else { iovecs[1].iov_len = avail - n; iovecs[1].iov_base = Bufs.first->buf; iovcnt = 2; avail = n; } iovecs[0].iov_len = avail; iovecs[0].iov_base = Bufs.first->buf + Bufs.first->head; if ((n = writev(WrtFD, iovecs, iovcnt)) <= 0) { if (n == 0) /* this should never happen */ ErrQuit("wrote 0 bytes!"); /* ignore error if we were blocked while writing */ if (errno != EAGAIN) ErrSys("write error"); } else { /* update buffer pointer, count */ Bufs.first->head += n; Bufs.first->head &= BUFMSK; Bufs.first->curlen -= n; /* free excess empty buffers */ if (Bufs.nbufs > 1 && Bufs.first->curlen == 0) free(RmFromBL(&Bufs)); } } } } /* ** Return a new buffer. Print error message and die ** if unsuccessful. */ BUF * GetNewBuf(void) { BUF *p; if ((p = (BUF *)malloc(sizeof(BUF))) == NULL) ErrSys("can't allocate buffer"); p->curlen = 0; p->head = p->tail = 0; return p; } /* ** Remove a buffer from a list. Returns NULL if ** the list is empty. */ BUF * RmFromBL(BUFL *blp) { BUF *p; if ((p = blp->first) != NULL) { /* return NULL if list is empty */ blp->first = p->next; --blp->nbufs; } return p; } /* ** Add a buffer to a list. No errors are possible here. */ void AddToBL(BUFL *blp, BUF *p) { p->next = NULL; if (blp->first == NULL) /* add to empty list */ blp->first = p; else blp->last->next = p; blp->last = p; ++blp->nbufs; } /* ** Non-fatal error related to a system call. ** Print a message and return. */ void ErrRet(const char *fmt, ...) { va_list ap; va_start(ap, fmt); ErrDoit(TRUE, fmt, ap); va_end(ap); } /* ** Fatal error related to a system call. ** Print a message and terminate. */ void ErrSys(const char *fmt, ...) { va_list ap; va_start(ap, fmt); ErrDoit(TRUE, fmt, ap); va_end(ap); exit(1); } /* ** Fatal error unrelated to a system call. ** Print a message and terminate. */ void ErrQuit(const char *fmt, ...) { va_list ap; va_start(ap, fmt); ErrDoit(FALSE, fmt, ap); va_end(ap); exit(1); } /* ** Print a message and return to caller. ** Caller specifies "errnoflag". */ void ErrDoit(int errnoflag, const char *fmt, va_list ap) { int ErrnoSave; char buf[MAXLINE]; ErrnoSave = errno; sprintf(buf, "%s: ", MyName); vsprintf(buf+strlen(buf), fmt, ap); if (errnoflag) sprintf(buf+strlen(buf), " (%s)", strerror(ErrnoSave)); strcat(buf, "\n"); fflush(stdout); /* in case stdout and stderr are the same */ fputs(buf, stderr); fflush(NULL); /* flushes all stdio output streams */ } -------------------CUT HERE------------------------