mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-11 05:14:46 -04:00
Add evbuffer_add_file_segment() so one fd can be used efficiently in more than one evbuffer_add_file at a time
This commit is contained in:
parent
076680abe4
commit
e72afae068
465
buffer.c
465
buffer.c
@ -63,6 +63,10 @@
|
||||
#ifdef _EVENT_HAVE_SYS_SENDFILE_H
|
||||
#include <sys/sendfile.h>
|
||||
#endif
|
||||
#ifdef _EVENT_HAVE_SYS_STAT_H
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
@ -111,14 +115,6 @@
|
||||
#define SENDFILE_IS_SOLARIS 1
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENDFILE
|
||||
static int use_sendfile = 1;
|
||||
#endif
|
||||
#ifdef _EVENT_HAVE_MMAP
|
||||
static int use_mmap = 1;
|
||||
#endif
|
||||
|
||||
|
||||
/* Mask of user-selectable callback flags. */
|
||||
#define EVBUFFER_CB_USER_FLAGS 0xffff
|
||||
/* Mask of all internal-use-only flags. */
|
||||
@ -144,13 +140,6 @@ static int evbuffer_ptr_memcmp(const struct evbuffer *buf,
|
||||
static struct evbuffer_chain *evbuffer_expand_singlechain(struct evbuffer *buf,
|
||||
size_t datlen);
|
||||
|
||||
#ifdef WIN32
|
||||
static int evbuffer_readfile(struct evbuffer *buf, evutil_socket_t fd,
|
||||
ev_ssize_t howmuch);
|
||||
#else
|
||||
#define evbuffer_readfile evbuffer_read
|
||||
#endif
|
||||
|
||||
static struct evbuffer_chain *
|
||||
evbuffer_chain_new(size_t size)
|
||||
{
|
||||
@ -187,8 +176,7 @@ evbuffer_chain_free(struct evbuffer_chain *chain)
|
||||
chain->flags |= EVBUFFER_DANGLING;
|
||||
return;
|
||||
}
|
||||
if (chain->flags & (EVBUFFER_MMAP|EVBUFFER_SENDFILE|
|
||||
EVBUFFER_REFERENCE)) {
|
||||
|
||||
if (chain->flags & EVBUFFER_REFERENCE) {
|
||||
struct evbuffer_chain_reference *info =
|
||||
EVBUFFER_CHAIN_EXTRA(
|
||||
@ -199,28 +187,13 @@ evbuffer_chain_free(struct evbuffer_chain *chain)
|
||||
chain->buffer_len,
|
||||
info->extra);
|
||||
}
|
||||
#ifdef _EVENT_HAVE_MMAP
|
||||
if (chain->flags & EVBUFFER_MMAP) {
|
||||
struct evbuffer_chain_fd *info =
|
||||
EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd,
|
||||
if (chain->flags & EVBUFFER_FILESEGMENT) {
|
||||
struct evbuffer_chain_file_segment *info =
|
||||
EVBUFFER_CHAIN_EXTRA(
|
||||
struct evbuffer_chain_file_segment,
|
||||
chain);
|
||||
if (munmap(chain->buffer, chain->buffer_len) == -1)
|
||||
event_warn("%s: munmap failed", __func__);
|
||||
if (close(info->fd) == -1)
|
||||
event_warn("%s: close(%d) failed",
|
||||
__func__, info->fd);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENDFILE
|
||||
if (chain->flags & EVBUFFER_SENDFILE) {
|
||||
struct evbuffer_chain_fd *info =
|
||||
EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd,
|
||||
chain);
|
||||
if (close(info->fd) == -1)
|
||||
event_warn("%s: close(%d) failed",
|
||||
__func__, info->fd);
|
||||
}
|
||||
#endif
|
||||
if (info->segment)
|
||||
evbuffer_file_segment_free(info->segment);
|
||||
}
|
||||
|
||||
mm_free(chain);
|
||||
@ -2124,56 +2097,6 @@ done:
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static int
|
||||
evbuffer_readfile(struct evbuffer *buf, evutil_socket_t fd, ev_ssize_t howmuch)
|
||||
{
|
||||
int result;
|
||||
int nchains, n;
|
||||
struct evbuffer_iovec v[2];
|
||||
|
||||
EVBUFFER_LOCK(buf);
|
||||
|
||||
if (buf->freeze_end) {
|
||||
result = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (howmuch < 0)
|
||||
howmuch = 16384;
|
||||
|
||||
|
||||
/* XXX we _will_ waste some space here if there is any space left
|
||||
* over on buf->last. */
|
||||
nchains = evbuffer_reserve_space(buf, howmuch, v, 2);
|
||||
if (nchains < 1 || nchains > 2) {
|
||||
result = -1;
|
||||
goto done;
|
||||
}
|
||||
n = read((int)fd, v[0].iov_base, (unsigned int)v[0].iov_len);
|
||||
if (n <= 0) {
|
||||
result = n;
|
||||
goto done;
|
||||
}
|
||||
v[0].iov_len = (IOV_LEN_TYPE) n; /* XXXX another problem with big n.*/
|
||||
if (nchains > 1) {
|
||||
n = read((int)fd, v[1].iov_base, (unsigned int)v[1].iov_len);
|
||||
if (n <= 0) {
|
||||
result = (unsigned long) v[0].iov_len;
|
||||
evbuffer_commit_space(buf, v, 1);
|
||||
goto done;
|
||||
}
|
||||
v[1].iov_len = n;
|
||||
}
|
||||
evbuffer_commit_space(buf, v, nchains);
|
||||
|
||||
result = n;
|
||||
done:
|
||||
EVBUFFER_UNLOCK(buf);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_IOVEC_IMPL
|
||||
static inline int
|
||||
evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
|
||||
@ -2225,44 +2148,46 @@ evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
|
||||
|
||||
#ifdef USE_SENDFILE
|
||||
static inline int
|
||||
evbuffer_write_sendfile(struct evbuffer *buffer, evutil_socket_t fd,
|
||||
evbuffer_write_sendfile(struct evbuffer *buffer, evutil_socket_t dest_fd,
|
||||
ev_ssize_t howmuch)
|
||||
{
|
||||
struct evbuffer_chain *chain = buffer->first;
|
||||
struct evbuffer_chain_fd *info =
|
||||
EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);
|
||||
struct evbuffer_chain_file_segment *info =
|
||||
EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_file_segment,
|
||||
chain);
|
||||
const int source_fd = info->segment->fd;
|
||||
#if defined(SENDFILE_IS_MACOSX) || defined(SENDFILE_IS_FREEBSD)
|
||||
int res;
|
||||
off_t len = chain->off;
|
||||
ev_off_t len = chain->off;
|
||||
#elif defined(SENDFILE_IS_LINUX) || defined(SENDFILE_IS_SOLARIS)
|
||||
ev_ssize_t res;
|
||||
off_t offset = chain->misalign;
|
||||
ev_off_t offset = chain->misalign;
|
||||
#endif
|
||||
|
||||
ASSERT_EVBUFFER_LOCKED(buffer);
|
||||
|
||||
#if defined(SENDFILE_IS_MACOSX)
|
||||
res = sendfile(info->fd, fd, chain->misalign, &len, NULL, 0);
|
||||
res = sendfile(source_fd, dest_fd, chain->misalign, &len, NULL, 0);
|
||||
if (res == -1 && !EVUTIL_ERR_RW_RETRIABLE(errno))
|
||||
return (-1);
|
||||
|
||||
return (len);
|
||||
#elif defined(SENDFILE_IS_FREEBSD)
|
||||
res = sendfile(info->fd, fd, chain->misalign, chain->off, NULL, &len, 0);
|
||||
res = sendfile(source_fd, dest_fd, chain->misalign, chain->off, NULL, &len, 0);
|
||||
if (res == -1 && !EVUTIL_ERR_RW_RETRIABLE(errno))
|
||||
return (-1);
|
||||
|
||||
return (len);
|
||||
#elif defined(SENDFILE_IS_LINUX)
|
||||
/* TODO(niels): implement splice */
|
||||
res = sendfile(fd, info->fd, &offset, chain->off);
|
||||
res = sendfile(dest_fd, source_fd, &offset, chain->off);
|
||||
if (res == -1 && EVUTIL_ERR_RW_RETRIABLE(errno)) {
|
||||
/* if this is EAGAIN or EINTR return 0; otherwise, -1 */
|
||||
return (0);
|
||||
}
|
||||
return (res);
|
||||
#elif defined(SENDFILE_IS_SOLARIS)
|
||||
res = sendfile(fd, info->fd, &offset, chain->off);
|
||||
res = sendfile(dest_fd, source_fd, &offset, chain->off);
|
||||
if (res == -1 && EVUTIL_ERR_RW_RETRIABLE(errno)) {
|
||||
/* if this is EAGAIN or EINTR return 0; otherwise, -1 */
|
||||
return (0);
|
||||
@ -2654,153 +2579,226 @@ done:
|
||||
return result;
|
||||
}
|
||||
|
||||
/* TODO(niels): maybe we don't want to own the fd, however, in that
|
||||
* case, we should dup it - dup is cheap. Perhaps, we should use a
|
||||
* callback instead?
|
||||
*/
|
||||
/* TODO(niels): we may want to add to automagically convert to mmap, in
|
||||
* case evbuffer_remove() or evbuffer_pullup() are being used.
|
||||
*/
|
||||
int
|
||||
evbuffer_add_file(struct evbuffer *outbuf, int fd,
|
||||
ev_off_t offset, ev_off_t length)
|
||||
struct evbuffer_file_segment *
|
||||
evbuffer_file_segment_new(
|
||||
int fd, ev_off_t offset, ev_off_t length, unsigned flags)
|
||||
{
|
||||
#if defined(USE_SENDFILE) || defined(_EVENT_HAVE_MMAP)
|
||||
struct evbuffer_chain *chain;
|
||||
struct evbuffer_chain_fd *info;
|
||||
struct evbuffer_file_segment *seg =
|
||||
mm_calloc(sizeof(struct evbuffer_file_segment), 1);
|
||||
if (!seg)
|
||||
return NULL;
|
||||
seg->refcnt = 1;
|
||||
seg->fd = fd;
|
||||
seg->flags = flags;
|
||||
|
||||
#ifdef WIN32
|
||||
#define lseek _lseeki64
|
||||
#define fstat _fstat
|
||||
#define stat _stat
|
||||
#endif
|
||||
int ok = 1;
|
||||
if (length == -1) {
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0)
|
||||
goto err;
|
||||
length = st.st_size;
|
||||
}
|
||||
seg->length = length;
|
||||
|
||||
#if defined(USE_SENDFILE)
|
||||
if (use_sendfile) {
|
||||
chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd));
|
||||
if (chain == NULL) {
|
||||
event_warn("%s: out of memory", __func__);
|
||||
return (-1);
|
||||
if (!(flags & EVBUF_FS_DISABLE_SENDFILE)) {
|
||||
seg->offset = offset;
|
||||
seg->type = EVBUF_FS_SENDFILE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
chain->flags |= EVBUFFER_SENDFILE | EVBUFFER_IMMUTABLE;
|
||||
chain->buffer = NULL; /* no reading possible */
|
||||
chain->buffer_len = length + offset;
|
||||
chain->off = length;
|
||||
chain->misalign = offset;
|
||||
|
||||
info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);
|
||||
info->fd = fd;
|
||||
|
||||
EVBUFFER_LOCK(outbuf);
|
||||
if (outbuf->freeze_end) {
|
||||
mm_free(chain);
|
||||
ok = 0;
|
||||
} else {
|
||||
outbuf->n_add_for_cb += length;
|
||||
evbuffer_chain_insert(outbuf, chain);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
#if defined(_EVENT_HAVE_MMAP)
|
||||
if (use_mmap) {
|
||||
void *mapped = mmap(NULL, length + offset, PROT_READ,
|
||||
#ifdef MAP_NOCACHE
|
||||
MAP_NOCACHE |
|
||||
#endif
|
||||
#ifdef MAP_FILE
|
||||
MAP_FILE |
|
||||
#endif
|
||||
MAP_PRIVATE,
|
||||
fd, 0);
|
||||
/* TODO: Implement an mmap-alike for windows. */
|
||||
if (!(flags & EVBUF_FS_DISABLE_MMAP)) {
|
||||
/* some mmap implementations require offset to be a multiple of
|
||||
* the page size. most users of this api, are likely to use 0
|
||||
* so mapping everything is not likely to be a problem.
|
||||
* TODO(niels): determine page size and round offset to that
|
||||
* page size to avoid mapping too much memory.
|
||||
*/
|
||||
void *mapped = mmap(NULL, length + offset, PROT_READ,
|
||||
#ifdef MAP_NOCACHE
|
||||
MAP_NOCACHE | /* ??? */
|
||||
#endif
|
||||
#ifdef MAP_FILE
|
||||
MAP_FILE |
|
||||
#endif
|
||||
MAP_PRIVATE,
|
||||
fd, 0);
|
||||
if (mapped == MAP_FAILED) {
|
||||
event_warn("%s: mmap(%d, %d, %zu) failed",
|
||||
__func__, fd, 0, (size_t)(offset + length));
|
||||
return (-1);
|
||||
}
|
||||
chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd));
|
||||
if (chain == NULL) {
|
||||
event_warn("%s: out of memory", __func__);
|
||||
munmap(mapped, length);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
chain->flags |= EVBUFFER_MMAP | EVBUFFER_IMMUTABLE;
|
||||
chain->buffer = mapped;
|
||||
chain->buffer_len = length + offset;
|
||||
chain->off = length + offset;
|
||||
|
||||
info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);
|
||||
info->fd = fd;
|
||||
|
||||
EVBUFFER_LOCK(outbuf);
|
||||
if (outbuf->freeze_end) {
|
||||
info->fd = -1;
|
||||
evbuffer_chain_free(chain);
|
||||
ok = 0;
|
||||
} else {
|
||||
outbuf->n_add_for_cb += length;
|
||||
|
||||
evbuffer_chain_insert(outbuf, chain);
|
||||
|
||||
/* we need to subtract whatever we don't need */
|
||||
evbuffer_drain(outbuf, offset);
|
||||
seg->mapping = mapped;
|
||||
seg->contents = ((char*)mapped)+offset;
|
||||
seg->offset = offset;
|
||||
seg->type = EVBUF_FS_MMAP;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
|
||||
{
|
||||
/* the default implementation */
|
||||
struct evbuffer *tmp = evbuffer_new();
|
||||
ev_ssize_t read;
|
||||
|
||||
if (tmp == NULL)
|
||||
return (-1);
|
||||
|
||||
#ifdef WIN32
|
||||
#define lseek _lseeki64
|
||||
#endif
|
||||
if (lseek(fd, offset, SEEK_SET) == -1) {
|
||||
evbuffer_free(tmp);
|
||||
return (-1);
|
||||
ev_off_t start_pos = lseek(fd, 0, SEEK_CUR), pos;
|
||||
ev_off_t read_so_far = 0;
|
||||
char *mem;
|
||||
int e;
|
||||
ev_ssize_t n = 0;
|
||||
if (!(mem = mm_malloc(length)))
|
||||
goto err;
|
||||
if (start_pos < 0) {
|
||||
mm_free(mem);
|
||||
goto err;
|
||||
}
|
||||
if (lseek(fd, offset, SEEK_SET) < 0) {
|
||||
mm_free(mem);
|
||||
goto err;
|
||||
}
|
||||
while (read_so_far < length) {
|
||||
n = read(fd, mem+read_so_far, length-read_so_far);
|
||||
if (n <= 0)
|
||||
break;
|
||||
read_so_far += n;
|
||||
}
|
||||
|
||||
/* we add everything to a temporary buffer, so that we
|
||||
* can abort without side effects if the read fails.
|
||||
*/
|
||||
while (length) {
|
||||
read = evbuffer_readfile(tmp, fd, (ev_ssize_t)length);
|
||||
if (read == -1) {
|
||||
evbuffer_free(tmp);
|
||||
return (-1);
|
||||
e = errno;
|
||||
pos = lseek(fd, start_pos, SEEK_SET);
|
||||
if (n < 0 || (n == 0 && length > read_so_far)) {
|
||||
mm_free(mem);
|
||||
errno = e;
|
||||
goto err;
|
||||
} else if (pos < 0) {
|
||||
mm_free(mem);
|
||||
goto err;
|
||||
}
|
||||
|
||||
length -= read;
|
||||
seg->contents = mem;
|
||||
seg->type = EVBUF_FS_IO;
|
||||
}
|
||||
|
||||
EVBUFFER_LOCK(outbuf);
|
||||
if (outbuf->freeze_end) {
|
||||
evbuffer_free(tmp);
|
||||
ok = 0;
|
||||
done:
|
||||
if (!(flags & EVBUF_FS_DISABLE_LOCKING)) {
|
||||
EVTHREAD_ALLOC_LOCK(seg->lock, 0);
|
||||
}
|
||||
return seg;
|
||||
err:
|
||||
mm_free(seg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
evbuffer_file_segment_free(struct evbuffer_file_segment *seg)
|
||||
{
|
||||
int refcnt;
|
||||
EVLOCK_LOCK(seg->lock, 0);
|
||||
refcnt = --seg->refcnt;
|
||||
EVLOCK_UNLOCK(seg->lock, 0);
|
||||
if (refcnt > 0)
|
||||
return;
|
||||
EVUTIL_ASSERT(refcnt == 0);
|
||||
|
||||
if (seg->type == EVBUF_FS_SENDFILE) {
|
||||
;
|
||||
} else if (seg->type == EVBUF_FS_MMAP) {
|
||||
if (munmap(seg->mapping, seg->length) == -1)
|
||||
event_warn("%s: munmap failed", __func__);
|
||||
} else {
|
||||
evbuffer_add_buffer(outbuf, tmp);
|
||||
evbuffer_free(tmp);
|
||||
|
||||
#ifdef WIN32
|
||||
#define close _close
|
||||
#endif
|
||||
close(fd);
|
||||
}
|
||||
EVUTIL_ASSERT(seg->type == EVBUF_FS_IO);
|
||||
mm_free(seg->contents);
|
||||
}
|
||||
|
||||
if (ok)
|
||||
evbuffer_invoke_callbacks(outbuf);
|
||||
EVBUFFER_UNLOCK(outbuf);
|
||||
|
||||
return ok ? 0 : -1;
|
||||
if ((seg->flags & EVBUF_FS_CLOSE_ON_FREE) && seg->fd >= 0) {
|
||||
close(seg->fd);
|
||||
}
|
||||
|
||||
EVTHREAD_FREE_LOCK(seg->lock, 0);
|
||||
mm_free(seg);
|
||||
}
|
||||
|
||||
int
|
||||
evbuffer_add_file_segment(struct evbuffer *buf,
|
||||
struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length)
|
||||
{
|
||||
struct evbuffer_chain *chain;
|
||||
struct evbuffer_chain_file_segment *extra;
|
||||
|
||||
EVLOCK_LOCK(seg->lock, 0);
|
||||
++seg->refcnt;
|
||||
EVLOCK_UNLOCK(seg->lock, 0);
|
||||
|
||||
EVBUFFER_LOCK(buf);
|
||||
|
||||
if (buf->freeze_end)
|
||||
goto err;
|
||||
|
||||
if (length < 0) {
|
||||
if (offset > seg->length)
|
||||
goto err;
|
||||
length = seg->length - offset;
|
||||
}
|
||||
|
||||
/* Can we actually add this? */
|
||||
if (offset+length > seg->length)
|
||||
goto err;
|
||||
|
||||
chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_file_segment));
|
||||
if (!chain)
|
||||
goto err;
|
||||
extra = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_file_segment, chain);
|
||||
|
||||
chain->flags |= EVBUFFER_IMMUTABLE|EVBUFFER_FILESEGMENT;
|
||||
if (seg->type == EVBUF_FS_SENDFILE) {
|
||||
chain->flags |= EVBUFFER_SENDFILE;
|
||||
chain->misalign = seg->offset + offset;
|
||||
chain->off = length;
|
||||
chain->buffer_len = chain->misalign + length;
|
||||
} else if (seg->type == EVBUF_FS_MMAP) {
|
||||
chain->buffer = (unsigned char*)(seg->contents + offset);
|
||||
chain->buffer_len = length;
|
||||
chain->off = length;
|
||||
} else {
|
||||
EVUTIL_ASSERT(seg->type == EVBUF_FS_IO);
|
||||
chain->buffer = (unsigned char*)(seg->contents + offset);
|
||||
chain->buffer_len = length;
|
||||
chain->off = length;
|
||||
}
|
||||
|
||||
extra->segment = seg;
|
||||
buf->n_add_for_cb += length;
|
||||
evbuffer_chain_insert(buf, chain);
|
||||
|
||||
evbuffer_invoke_callbacks(buf);
|
||||
|
||||
EVBUFFER_UNLOCK(buf);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
EVBUFFER_UNLOCK(buf);
|
||||
evbuffer_file_segment_free(seg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
evbuffer_add_file(struct evbuffer *buf, int fd, ev_off_t offset, ev_off_t length)
|
||||
{
|
||||
struct evbuffer_file_segment *seg;
|
||||
unsigned flags = EVBUF_FS_CLOSE_ON_FREE;
|
||||
int r;
|
||||
|
||||
seg = evbuffer_file_segment_new(fd, offset, length, flags);
|
||||
if (!seg)
|
||||
return -1;
|
||||
r = evbuffer_add_file_segment(buf, seg, 0, length);
|
||||
evbuffer_file_segment_free(seg);
|
||||
return r;
|
||||
}
|
||||
|
||||
void
|
||||
evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg)
|
||||
@ -2936,50 +2934,3 @@ evbuffer_cb_unsuspend(struct evbuffer *buffer, struct evbuffer_cb_entry *cb)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* These hooks are exposed so that the unit tests can temporarily disable
|
||||
* sendfile support in order to test mmap, or both to test linear
|
||||
* access. Don't use it; if we need to add a way to disable sendfile support
|
||||
* in the future, it will probably be via an alternate version of
|
||||
* evbuffer_add_file() with a 'flags' argument.
|
||||
*/
|
||||
int _evbuffer_testing_use_sendfile(void);
|
||||
int _evbuffer_testing_use_mmap(void);
|
||||
int _evbuffer_testing_use_linear_file_access(void);
|
||||
|
||||
int
|
||||
_evbuffer_testing_use_sendfile(void)
|
||||
{
|
||||
int ok = 0;
|
||||
#ifdef USE_SENDFILE
|
||||
use_sendfile = 1;
|
||||
ok = 1;
|
||||
#endif
|
||||
#ifdef _EVENT_HAVE_MMAP
|
||||
use_mmap = 0;
|
||||
#endif
|
||||
return ok;
|
||||
}
|
||||
int
|
||||
_evbuffer_testing_use_mmap(void)
|
||||
{
|
||||
int ok = 0;
|
||||
#ifdef USE_SENDFILE
|
||||
use_sendfile = 0;
|
||||
#endif
|
||||
#ifdef _EVENT_HAVE_MMAP
|
||||
use_mmap = 1;
|
||||
ok = 1;
|
||||
#endif
|
||||
return ok;
|
||||
}
|
||||
int
|
||||
_evbuffer_testing_use_linear_file_access(void)
|
||||
{
|
||||
#ifdef USE_SENDFILE
|
||||
use_sendfile = 0;
|
||||
#endif
|
||||
#ifdef _EVENT_HAVE_MMAP
|
||||
use_mmap = 0;
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
@ -170,8 +170,8 @@ struct evbuffer_chain {
|
||||
|
||||
/** Set if special handling is required for this chain */
|
||||
unsigned flags;
|
||||
#define EVBUFFER_MMAP 0x0001 /**< memory in buffer is mmaped */
|
||||
#define EVBUFFER_SENDFILE 0x0002 /**< a chain used for sendfile */
|
||||
#define EVBUFFER_FILESEGMENT 0x0001 /**< A chain used for a file segment */
|
||||
#define EVBUFFER_SENDFILE 0x0002 /**< a chain used with sendfile */
|
||||
#define EVBUFFER_REFERENCE 0x0004 /**< a chain with a mem reference */
|
||||
#define EVBUFFER_IMMUTABLE 0x0008 /**< read-only chain */
|
||||
/** a chain that mustn't be reallocated or freed, or have its contents
|
||||
@ -192,21 +192,45 @@ struct evbuffer_chain {
|
||||
unsigned char *buffer;
|
||||
};
|
||||
|
||||
/* this is currently used by both mmap and sendfile */
|
||||
/* TODO(niels): something strange needs to happen for Windows here, I am not
|
||||
* sure what that is, but it needs to get looked into.
|
||||
*/
|
||||
struct evbuffer_chain_fd {
|
||||
int fd; /**< the fd associated with this chain */
|
||||
};
|
||||
|
||||
/** callback for a reference buffer; lets us know what to do with it when
|
||||
* we're done with it. */
|
||||
/** callback for a reference chain; lets us know what to do with it when
|
||||
* we're done with it. Lives at the end of an evbuffer_chain with the
|
||||
* EVBUFFER_REFERENCE flag set */
|
||||
struct evbuffer_chain_reference {
|
||||
evbuffer_ref_cleanup_cb cleanupfn;
|
||||
void *extra;
|
||||
};
|
||||
|
||||
/** File segment for a file-segment chain. Lives at the end of an
|
||||
* evbuffer_chain with the EVBUFFER_FILESEGMENT flag set. */
|
||||
struct evbuffer_chain_file_segment {
|
||||
struct evbuffer_file_segment *segment;
|
||||
};
|
||||
|
||||
/* Declared in event2/buffer.h; defined here. */
|
||||
struct evbuffer_file_segment {
|
||||
void *lock; /**< lock prevent concurrent access to refcnt */
|
||||
int refcnt; /**< Reference count for this file segment */
|
||||
unsigned flags; /**< combination of EVBUF_FS_* flags */
|
||||
|
||||
/** What kind of file segment is this? */
|
||||
enum {EVBUF_FS_MMAP, EVBUF_FS_SENDFILE, EVBUF_FS_IO} type;
|
||||
|
||||
/** The fd that we read the data from. */
|
||||
int fd;
|
||||
/** If we're using mmap, this is the raw mapped memory. */
|
||||
void *mapping;
|
||||
/** If we're using mmap or IO, this is the content of the file
|
||||
* segment. */
|
||||
char *contents;
|
||||
/** If we're using mmap, this is the offset within 'mapping' where
|
||||
* this data segment begins. If we're using sendfile, this is the
|
||||
* offset within the file where this data begins. If we're using IO,
|
||||
* this is 0. */
|
||||
off_t offset;
|
||||
/** The length of this segment. */
|
||||
off_t length;
|
||||
};
|
||||
|
||||
#define EVBUFFER_CHAIN_SIZE sizeof(struct evbuffer_chain)
|
||||
/** Return a pointer to extra data allocated along with an evbuffer. */
|
||||
#define EVBUFFER_CHAIN_EXTRA(t, c) (t *)((struct evbuffer_chain *)(c) + 1)
|
||||
|
@ -368,16 +368,117 @@ int evbuffer_add_reference(struct evbuffer *outbuf,
|
||||
The results of using evbuffer_remove() or evbuffer_pullup() are
|
||||
undefined.
|
||||
|
||||
For more fine-grained control, use evbuffer_add_file_segment.
|
||||
|
||||
@param outbuf the output buffer
|
||||
@param fd the file descriptor
|
||||
@param off the offset from which to read data
|
||||
@param length how much data to read
|
||||
@param length how much data to read, or -1 to read as much as possible.
|
||||
(-1 requires that 'fd' support fstat.)
|
||||
@return 0 if successful, or -1 if an error occurred
|
||||
*/
|
||||
|
||||
int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset,
|
||||
ev_off_t length);
|
||||
|
||||
/**
|
||||
An evbuffer_file_segment holds a reference to a range of a file --
|
||||
possibly the whole file! -- for use in writing from an evbuffer to a
|
||||
socket. It could be implemented with mmap, sendfile, splice, or (if all
|
||||
else fails) by just pulling all the data into RAM. A single
|
||||
evbuffer_file_segment can be added more than once, and to more than one
|
||||
evbuffer.
|
||||
*/
|
||||
struct evbuffer_file_segment;
|
||||
|
||||
/**
|
||||
Flag for creating evbuffer_file_segment: If this flag is set, then when
|
||||
the evbuffer_file_segment is freed and no longer in use by any
|
||||
evbuffer, the underlying fd is closed.
|
||||
*/
|
||||
#define EVBUF_FS_CLOSE_ON_FREE 0x01
|
||||
/**
|
||||
Flag for creating evbuffer_file_segment: Disable memory-map based
|
||||
implementations.
|
||||
*/
|
||||
#define EVBUF_FS_DISABLE_MMAP 0x02
|
||||
/**
|
||||
Flag for creating evbuffer_file_segment: Disable direct fd-to-fd
|
||||
implementations (including sendfile and splice).
|
||||
|
||||
You might want to use this option if data needs to be taken from the
|
||||
evbuffer by any means other than writing it to the network: the sendfile
|
||||
backend is fast, but it only works for sending files directly to the
|
||||
network.
|
||||
*/
|
||||
#define EVBUF_FS_DISABLE_SENDFILE 0x04
|
||||
/**
|
||||
Flag for creating evbuffer_file_segment: Do not allocate a lock for this
|
||||
segment. If this option is set, then neither the segment nor any
|
||||
evbuffer it is added to may ever be accessed from more than one thread
|
||||
at a time.
|
||||
*/
|
||||
#define EVBUF_FS_DISABLE_LOCKING 0x08
|
||||
|
||||
/**
|
||||
Create and return a new evbuffer_file_segment for reading data from a
|
||||
file and sending it out via an evbuffer.
|
||||
|
||||
This function avoids unnecessary data copies between userland and
|
||||
kernel. Where available, it uses sendfile or splice.
|
||||
|
||||
The file descriptor must not be closed so long as any evbuffer is using
|
||||
this segment.
|
||||
|
||||
The results of using evbuffer_remove() or evbuffer_pullup() or any other
|
||||
function that reads bytes from an evbuffer on any evbuffer containing
|
||||
the newly returned segment are undefined, unless you pass the
|
||||
EVBUF_FS_DISABLE_SENDFILE flag to this function.
|
||||
|
||||
@param fd an open file to read from.
|
||||
@param offset an index within the file at which to start reading
|
||||
@param length how much data to read, or -1 to read as much as possible.
|
||||
(-1 requires that 'fd' support fstat.)
|
||||
@param flags any number of the EVBUF_FS_* flags
|
||||
@return a new evbuffer_file_segment, or NULL on failure.
|
||||
**/
|
||||
struct evbuffer_file_segment *evbuffer_file_segment_new(
|
||||
int fd, ev_off_t offset, ev_off_t length, unsigned flags);
|
||||
|
||||
/**
|
||||
Free an evbuffer_file_segment
|
||||
|
||||
It is safe to call this function even if the segment has been added to
|
||||
one or more evbuffers. The evbuffer_file_segment will not be freed
|
||||
until no more references to it exist.
|
||||
*/
|
||||
void evbuffer_file_segment_free(struct evbuffer_file_segment *seg);
|
||||
|
||||
/**
|
||||
Insert some or all of an evbuffer_file_segment at the end of an evbuffer
|
||||
|
||||
Note that the offset and length parameters of this function have a
|
||||
different meaning from those provided to evbuffer_file_segment_new: When
|
||||
you create the segment, the offset is the offset _within the file_, and
|
||||
the length is the length _of the segment_, whereas when you add a
|
||||
segment to an evbuffer, the offset is _within the segment_ and the
|
||||
length is the length of the _part of the segment you want to use.
|
||||
|
||||
In other words, if you have a 10 KiB file, and you create an
|
||||
evbuffer_file_segment for it with offset 20 and length 1000, it will
|
||||
refer to bytes 20..1019 inclusive. If you then pass this segment to
|
||||
evbuffer_add_file_segment and specify an offset of 20 and a length of
|
||||
50, you will be adding bytes 40..99 inclusive.
|
||||
|
||||
@param buf the evbuffer to append to
|
||||
@param seg the segment to add
|
||||
@param offset the offset within the segment to start from
|
||||
@param length the amount of data to add, or -1 to add it all.
|
||||
@return 0 on success, -1 on failure.
|
||||
*/
|
||||
int evbuffer_add_file_segment(struct evbuffer *buf,
|
||||
struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length);
|
||||
|
||||
/**
|
||||
Append a formatted string to the end of an evbuffer.
|
||||
|
||||
|
@ -583,10 +583,6 @@ test_evbuffer_reference(void *ptr)
|
||||
evbuffer_free(src);
|
||||
}
|
||||
|
||||
int _evbuffer_testing_use_sendfile(void);
|
||||
int _evbuffer_testing_use_mmap(void);
|
||||
int _evbuffer_testing_use_linear_file_access(void);
|
||||
|
||||
static void
|
||||
test_evbuffer_add_file(void *ptr)
|
||||
{
|
||||
@ -598,26 +594,39 @@ test_evbuffer_add_file(void *ptr)
|
||||
int fd = -1;
|
||||
evutil_socket_t pair[2] = {-1, -1};
|
||||
int r=0, n_written=0;
|
||||
int want_type = 0;
|
||||
unsigned flags = 0;
|
||||
int use_segment = 1;
|
||||
struct evbuffer_file_segment *seg = NULL;
|
||||
|
||||
/* Add a test for a big file. XXXX */
|
||||
|
||||
tt_assert(impl);
|
||||
if (!strcmp(impl, "sendfile")) {
|
||||
if (!_evbuffer_testing_use_sendfile())
|
||||
tt_skip();
|
||||
TT_BLATHER(("Using sendfile-based implementaion"));
|
||||
if (!strcmp(impl, "nosegment")) {
|
||||
use_segment = 0;
|
||||
} else if (!strcmp(impl, "sendfile")) {
|
||||
flags = EVBUF_FS_DISABLE_MMAP;
|
||||
want_type = EVBUF_FS_SENDFILE;
|
||||
} else if (!strcmp(impl, "mmap")) {
|
||||
if (!_evbuffer_testing_use_mmap())
|
||||
tt_skip();
|
||||
TT_BLATHER(("Using mmap-based implementaion"));
|
||||
flags = EVBUF_FS_DISABLE_SENDFILE;
|
||||
want_type = EVBUF_FS_MMAP;
|
||||
} else if (!strcmp(impl, "linear")) {
|
||||
if (!_evbuffer_testing_use_linear_file_access())
|
||||
tt_skip();
|
||||
TT_BLATHER(("Using read-based implementaion"));
|
||||
flags = EVBUF_FS_DISABLE_SENDFILE|EVBUF_FS_DISABLE_MMAP;
|
||||
want_type = EVBUF_FS_IO;
|
||||
} else {
|
||||
TT_DIE(("Didn't recognize the implementation"));
|
||||
}
|
||||
|
||||
datalen = strlen(data);
|
||||
fd = regress_make_tmpfile(data, datalen);
|
||||
|
||||
if (use_segment) {
|
||||
seg = evbuffer_file_segment_new(fd, 0, datalen, flags);
|
||||
tt_assert(seg);
|
||||
if ((int)seg->type != (int)want_type)
|
||||
tt_skip();
|
||||
}
|
||||
|
||||
#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. */
|
||||
@ -628,12 +637,13 @@ test_evbuffer_add_file(void *ptr)
|
||||
tt_abort_msg("socketpair failed");
|
||||
#endif
|
||||
|
||||
datalen = strlen(data);
|
||||
fd = regress_make_tmpfile(data, datalen);
|
||||
|
||||
tt_assert(fd != -1);
|
||||
|
||||
tt_assert(evbuffer_add_file(src, fd, 0, datalen) != -1);
|
||||
if (use_segment) {
|
||||
tt_assert(evbuffer_add_file_segment(src, seg, 0, -1)!=-1);
|
||||
} else {
|
||||
tt_assert(evbuffer_add_file(src, fd, 0, -1) != -1);
|
||||
}
|
||||
|
||||
evbuffer_validate(src);
|
||||
|
||||
@ -650,8 +660,9 @@ test_evbuffer_add_file(void *ptr)
|
||||
evbuffer_validate(src);
|
||||
compare = (char *)evbuffer_pullup(src, datalen);
|
||||
tt_assert(compare != NULL);
|
||||
if (memcmp(compare, data, datalen))
|
||||
if (memcmp(compare, data, datalen)) {
|
||||
tt_abort_msg("Data from add_file differs.");
|
||||
}
|
||||
|
||||
evbuffer_validate(src);
|
||||
end:
|
||||
@ -660,6 +671,8 @@ test_evbuffer_add_file(void *ptr)
|
||||
if (pair[1] >= 0)
|
||||
evutil_closesocket(pair[1]);
|
||||
evbuffer_free(src);
|
||||
if (seg)
|
||||
evbuffer_file_segment_free(seg);
|
||||
}
|
||||
|
||||
#ifndef _EVENT_DISABLE_MM_REPLACEMENT
|
||||
@ -1562,6 +1575,8 @@ struct testcase_t evbuffer_testcases[] = {
|
||||
(void*)"mmap" },
|
||||
{ "add_file_linear", test_evbuffer_add_file, TT_FORK, &nil_setup,
|
||||
(void*)"linear" },
|
||||
{ "add_file_nosegment", test_evbuffer_add_file, TT_FORK, &nil_setup,
|
||||
(void*)"nosegment" },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user