Prefer mmap to sendfile unless a DRAINS_TO_FD flag is set. Allows add_file to work with SSL.

The sendfile() implementation for evbuffer_add_file is potentially more
efficient, but it has a problem: you can only use it to send bytes over
a socket using sendfile().  If you are writing bytes via SSL_send() or
via a filter, or if you need to be able to inspect your buffer, it
doesn't work.

As an easy fix, this patch disables the sendfile-based implementation of
evbuffer_add_file on an evbuffer unless the user sets a new
EVBUFFER_FLAG_DRAINS_TO_FD flag on that evbuffer, indicating that the
evbuffer will not be inspected, but only written out via
evbuffer_write(), evbuffer_write_atmost(), or drained with stuff like
evbuffer_drain() or evbuffer_add_buffer().  This flag is off by
default, except for evbuffers used for output on bufferevent_socket.

In the future, it could be interesting to make a best-effort file
segment implementation that tries to send via sendfile, but mmaps on
demand.  That's too much complexity for a stable release series, though.
This commit is contained in:
Nick Mathewson 2011-09-29 09:30:04 -04:00
parent 1ef1f68462
commit 0ba0af9c6c
5 changed files with 71 additions and 3 deletions

View File

@ -327,6 +327,24 @@ evbuffer_new(void)
return (buffer);
}
int
evbuffer_set_flags(struct evbuffer *buf, ev_uint64_t flags)
{
EVBUFFER_LOCK(buf);
buf->flags |= (ev_uint32_t)flags;
EVBUFFER_UNLOCK(buf);
return 0;
}
int
evbuffer_clear_flags(struct evbuffer *buf, ev_uint64_t flags)
{
EVBUFFER_LOCK(buf);
buf->flags &= ~(ev_uint32_t)flags;
EVBUFFER_UNLOCK(buf);
return 0;
}
void
_evbuffer_incref(struct evbuffer *buf)
{
@ -2680,11 +2698,20 @@ evbuffer_add_file(struct evbuffer *outbuf, int fd,
#if defined(USE_SENDFILE) || defined(_EVENT_HAVE_MMAP)
struct evbuffer_chain *chain;
struct evbuffer_chain_fd *info;
#endif
#if defined(USE_SENDFILE)
int sendfile_okay = 1;
#endif
int ok = 1;
#if defined(USE_SENDFILE)
if (use_sendfile) {
EVBUFFER_LOCK(outbuf);
sendfile_okay = outbuf->flags & EVBUFFER_FLAG_DRAINS_TO_FD;
EVBUFFER_UNLOCK(outbuf);
}
if (use_sendfile && sendfile_okay) {
chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd));
if (chain == NULL) {
event_warn("%s: out of memory", __func__);

View File

@ -328,6 +328,7 @@ bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
return NULL;
}
bufev = &bufev_p->bev;
evbuffer_set_flags(bufev->output, EVBUFFER_FLAG_DRAINS_TO_FD);
event_assign(&bufev->ev_read, bufev->ev_base, fd,
EV_READ|EV_PERSIST, bufferevent_readcb, bufev);

View File

@ -129,6 +129,8 @@ struct evbuffer {
/** True iff this buffer is set up for overlapped IO. */
unsigned is_overlapped : 1;
#endif
/** Zero or more EVBUFFER_FLAG_* bits */
ev_uint32_t flags;
/** Used to implement deferred callbacks. */
struct deferred_cb_queue *cb_queue;

View File

@ -141,7 +141,6 @@ struct evbuffer_iovec {
occurred
*/
struct evbuffer *evbuffer_new(void);
/**
Deallocate storage for an evbuffer.
@ -175,6 +174,41 @@ void evbuffer_lock(struct evbuffer *buf);
*/
void evbuffer_unlock(struct evbuffer *buf);
/** If this flag is set, then we will not use evbuffer_peek(),
* evbuffer_remove(), evbuffer_remove_buffer(), and so on to read bytes
* from this buffer: we'll only take bytes out of this buffer by
* writing them to the network (as with evbuffer_write_atmost), by
* removing them without observing them (as with evbuffer_drain),
* or by copying them all out at once (as with evbuffer_add_buffer).
*
* Using this option allows the implementation to use sendfile-based
* operations for evbuffer_add_file(); see that function for more
* information.
*
* This flag is on by default for bufferevents that can take advantage
* of it; you should never actually need to set it on a bufferevent's
* output buffer.
*/
#define EVBUFFER_FLAG_DRAINS_TO_FD 1
/** Change the flags that are set for an evbuffer by adding more.
*
* @param buffer the evbuffer that the callback is watching.
* @param cb the callback whose status we want to change.
* @param flags One or more EVBUFFER_FLAG_* options
* @return 0 on success, -1 on failure.
*/
int evbuffer_set_flags(struct evbuffer *buf, ev_uint64_t flags);
/** Change the flags that are set for an evbuffer by removing some.
*
* @param buffer the evbuffer that the callback is watching.
* @param cb the callback whose status we want to change.
* @param flags One or more EVBUFFER_FLAG_* options
* @return 0 on success, -1 on failure.
*/
int evbuffer_clear_flags(struct evbuffer *buf, ev_uint64_t flags);
/**
Returns the total number of bytes stored in the evbuffer
@ -408,8 +442,9 @@ int evbuffer_add_reference(struct evbuffer *outbuf,
Copy data from a file into the evbuffer for writing to a socket.
This function avoids unnecessary data copies between userland and
kernel. Where available, it uses sendfile or splice; failing those,
it tries to use mmap.
kernel. If sendfile is available and the EVBUFFER_FLAG_DRAINS_TO_FD
flag is set, it uses those functions. Otherwise, it tries to use
mmap (or CreateFileMapping on Windows).
The function owns the resulting file descriptor and will close it
when finished transferring data.

View File

@ -618,6 +618,9 @@ test_evbuffer_add_file(void *ptr)
TT_DIE(("Didn't recognize the implementation"));
}
/* Say that it drains to a fd so that we can use sendfile. */
evbuffer_set_flags(src, EVBUFFER_FLAG_DRAINS_TO_FD);
#if defined(_EVENT_HAVE_SENDFILE) && defined(__sun__) && defined(__svr4__)
/* We need to use a pair of AF_INET sockets, since Solaris
doesn't support sendfile() over AF_UNIX. */