[Libevent-users] Patch: useful sendfile for HTTP servers (1.4.3-stable)

2008-05-08 Thread Manuel Simoni
[Resending because apparently it didn't get through yesterday.]

Hi,

my HTTP server thingy came to a screeching halt when serving many
large files, so I added a form of Linux sendfile support that requires
just a few changes and supports some interesting uses (e.g. sending
the HTTP headers normally but the HTTP body via sendfile).

The server now runs very fast, with very little CPU and RAM.  This is
preliminary, probably buggy, and I don't understand the evbuffer
functionality very well (e.g. draining).


In addition to the normal data evbuffers, this patch adds two types of
evbuffers: (1) sendfile buffers, and (2) data buffers to which a
sendfile buffer has been added.  This distinction is recorded in the
sf_fd field.  There are two additonal fields, sf_off and sf_buf.


*** libevent-1.4.3-stable/event.h   2008-02-23 02:36:12.0 +0100
--- libevent-1.4.3-stable-sendfile/event.h  2008-05-07
15:40:32.0 +0200
***
*** 724,729 
--- 724,746 
size_t totallen;
size_t off;

+   /*
+ sf_fd == -1.  A standard data buffer.  sf_off and sf_buf are
+ unused.
+
+ sf_fd == -2.  A buffer to which a sendfile buffer has been
+ added at the end (sf_buf).  off holds the combined size of
+ the RAM data of this buffer and the added sendfile buffer.
+ sf_off is unused.
+
+ sf_fd = 0.  A pure sendfile buffer.  sf_fd, sf_off, and off
+ are the arguments in_fd, offset, and count to sendfile(),
+ respectively.  sf_buf is unused.
+   */
+   int sf_fd;
+   off_t sf_off;
+   struct evbuffer *sf_buf;
+
void (*cb)(struct evbuffer *, size_t, size_t, void *);
void *cbarg;
  };




The adding and writing of buffers (evbuffer_add_buffer,
evbuffer_write) is changed:

To keep it simple this system only allows adding a sendfile buffer to
a data buffer.  So, the sendfile buffer must be the last step in the
chain, which works fine for e.g. many HTTP uses cases.  You cannot add
a sendfile buffer to another sendfile buffer, or add a data buffer to
a sendfile buffer.  I think these things would theoretically be
possible, but require a lot more housekeeping than the current
solution, which plugs directly into the existing draining code.

When a sendfile buffer is added after a data buffer, we trick and
increment the data buffer's length by the length of the sendfile
buffer, without actually copying the data to the buffer (which would
completely defeat the purpose of sendfile, of course).  Then, later
when it comes to writing this combined buffer, we just need to check
whether all normal data has been written, and then switch over to
writing the sendfile data.  This *seems* to work nicely but I am not
100% sure that every situation is handled OK.


*** libevent-1.4.3-stable/buffer.c  2007-11-12 03:37:32.0 +0100
--- libevent-1.4.3-stable-sendfile/buffer.c 2008-05-07
16:36:44.0 +0200
***
*** 61,66 
--- 61,68 
  #include unistd.h
  #endif

+ #include sys/sendfile.h
+
  #include event.h
  #include config.h

***
*** 71,76 
--- 73,81 

buffer = calloc(1, sizeof(struct evbuffer));

+   /* Make all buffers non-sendfile buffers initially. */
+   buffer-sf_fd = -1;
+
return (buffer);
  }

***
*** 100,105 
--- 105,130 
  {
int res;

+   if (outbuf-sf_fd == -1) {
+   /* The output buffer is a data buffer. */
+   if (inbuf-sf_fd = 0) {
+   /* The input buffer is a sendfile buffer.
+  Mark this buffer as containing a sendfile
+  buffer and virtually extend the buffer by
+  the size of the sendfile buffer. */
+   outbuf-sf_fd = -2;
+   outbuf-sf_buf = inbuf;
+   outbuf-off += inbuf-off;
+   return 0;
+   }
+   } else {
+   /* No support for nested or concatenated sendfile
+  buffers, i.e. if the output buffer is or contains a
+  sendfile buffer, we cannot add another sendfile
+  buffer to it. */
+   return -1;
+   }
+
/* Short cut for better performance */
if (outbuf-off == 0) {
struct evbuffer tmp;
***
*** 415,421 
int n;

  #ifndef WIN32
!   n = write(fd, buffer-buffer, buffer-off);
  #else
n = send(fd, buffer-buffer, buffer-off, 0);
  #endif
--- 440,467 
int n;

  #ifndef WIN32
!   switch(buffer-sf_fd) {
!   case -1:
!   /* A data buffer. */
!   n = write(fd, buffer-buffer, buffer-off);
!   break;
!   case -2:
!   /* A data buffer with a sendfile buffer at the end.
!  First write the actual data in the buffer, then
!  switch to 

Re: [Libevent-users] Patch: useful sendfile for HTTP servers (1.4.3-stable)

2008-05-08 Thread Nick Mathewson
On Wed, May 07, 2008 at 11:47:09PM -0700, Niels Provos wrote:
 Hi Manual,
 
 this is a good suggestion.   Nick and I are currently working on how
 buffers and http work in libevent 2.0.  You might want to check out
 trunk to see some of the progress there.   In any case, it seems that
 your sendfile changes would be a good fit there.  BTW, sendfile is
 available on a large number of platforms now.

In fact, this fits pretty well into the newer evbuffer implementation.
Whereas the old implementation used a big chunk of memory for the
entire buffer, the new code uses a linked list of small chunks. (This
removes the need for big memmov operations, and generally makes
writing big files more efficient.)

All we'd need to do to support sendfile is add another chunk type
whose contents were a file rather than a chunk of ram.  We'd probably
want at least two ways to add a file at this point in the stream:
one taking a filename and one taking an fd.  Of course, we'd need to
make sure to fall back on mmap() for systems lacking sendfile(), and
maybe on a series of read() operations on systems lacking mmap().

yrs,
-- 
Nick
___
Libevent-users mailing list
Libevent-users@monkey.org
http://monkeymail.org/mailman/listinfo/libevent-users