Index: chunk.c --- chunk.c.orig 2005-11-18 06:18:19.000000000 -0700 +++ chunk.c 2007-05-24 18:13:08.000000000 -0600 @@ -325,6 +325,26 @@ return len; } +off_t chunkqueue_memory_used(chunkqueue *cq) { + off_t used = 0; + chunk *c; + + for (c = cq->first; c; c = c->next) { + used += sizeof *c; + switch (c->type) { + case MEM_CHUNK: + used += c->mem->used; + break; + case FILE_CHUNK: + break; + default: + break; + } + } + + return used; +} + off_t chunkqueue_written(chunkqueue *cq) { off_t len = 0; chunk *c; Index: chunk.h --- chunk.h.orig 2005-11-01 00:32:21.000000000 -0700 +++ chunk.h 2007-05-24 18:13:08.000000000 -0600 @@ -60,6 +60,7 @@ int chunkqueue_remove_finished_chunks(chunkqueue *cq); off_t chunkqueue_length(chunkqueue *c); +off_t chunkqueue_memory_used(chunkqueue *c); off_t chunkqueue_written(chunkqueue *c); void chunkqueue_free(chunkqueue *c); void chunkqueue_reset(chunkqueue *c); Index: mod_fastcgi.c --- mod_fastcgi.c.orig 2006-03-09 04:18:39.000000000 -0700 +++ mod_fastcgi.c 2007-05-24 18:19:13.000000000 -0600 @@ -326,9 +326,34 @@ FCGI_STATE_CONNECT_DELAYED, FCGI_STATE_PREPARE_WRITE, FCGI_STATE_WRITE, + FCGI_STATE_WRITE_QUEUE_FULL, FCGI_STATE_READ } fcgi_connection_state_t; +/* + * Limit on how large any write queue is allowed to grow. If the + * queue becomes larger than this, lighttpd will temporarily remove + * the *input* side fd from the event handler until the queue length + * drops. Because of the hacky way in which this feature is + * implemented, the reduced queue length won't be noticed for up to + * one second. For that reason, the queue length should be large + * enough to sustain 2-3 seconds of output across the server's + * connection. On a 100 Mbps Ethernet, that works out to about 25 MB. + * Note that a machine that serves many connections might still run + * out of memory (for example, if MAX_WRITE_QUEUE_SIZE is 32 MB, 100 + * connections will expect the server to allocate 3.2 GB, which most + * systems can't handle). + * + * The correct way to implement this feature is to have + * MAX_WRITE_QUEUE_SIZE fairly small, probably around 1 MB, but have + * the wakeup done by chunk.c when the chunk queue drains + * appropriately. That requires more extensive changes, and I'm + * trying to limit the amount of code I change, so I'll leave that + * improvement to someone more familiar with lighttpd. Geoff + * Kuenning, 5/24/2007. + */ +#define MAX_WRITE_QUEUE_SIZE (1 << 25) /* 32 MB */ + typedef struct { fcgi_proc *proc; fcgi_extension_host *host; @@ -2395,6 +2420,16 @@ fcgi_extension_host *host= hctx->host; fcgi_proc *proc = hctx->proc; + /* + * Before reading, find out if the write queue is overfull. + * If so, suspend reading. Geoff Kuenning, 5/24/2007. + */ + if (chunkqueue_memory_used(con->write_queue) >= MAX_WRITE_QUEUE_SIZE) { + fdevent_event_del(srv->ev, &(hctx->fde_ndx), fcgi_fd); + fcgi_set_state(srv, hctx, FCGI_STATE_WRITE_QUEUE_FULL); + return 0; + } + /* * check how much we have to read */ @@ -2983,6 +3018,18 @@ case FCGI_STATE_READ: /* waiting for a response */ break; + case FCGI_STATE_WRITE_QUEUE_FULL: + /* + * We stopped reading because the write queue is full. + * See if we can read again. If so, put the fd back + * in the event list. Geoff Kuenning, 5/24/2007. + */ + if (chunkqueue_memory_used(con->write_queue) < MAX_WRITE_QUEUE_SIZE) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fcgi_set_state(srv, hctx, FCGI_STATE_READ); + + } + break; default: log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state"); return HANDLER_ERROR; @@ -3580,6 +3627,19 @@ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); break; + case FCGI_STATE_WRITE_QUEUE_FULL: + /* + * We stopped reading because the write queue + * is full. See if we can read again. If so, + * put the fd back in the event list. Geoff + * Kuenning, 5/24/2007. + */ + if (chunkqueue_memory_used(con->write_queue) < MAX_WRITE_QUEUE_SIZE) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fcgi_set_state(srv, hctx, FCGI_STATE_READ); + + } + break; case FCGI_STATE_INIT: /* at reconnect */ break;