Merge branch 'evbuffer_insert_point'

This commit is contained in:
Nick Mathewson 2010-03-12 12:26:06 -05:00
commit 6c83e6c972
4 changed files with 356 additions and 189 deletions

395
buffer.c
View File

@ -219,18 +219,20 @@ evbuffer_chain_insert(struct evbuffer *buf, struct evbuffer_chain *chain)
ASSERT_EVBUFFER_LOCKED(buf); ASSERT_EVBUFFER_LOCKED(buf);
if (buf->first == NULL) { if (buf->first == NULL) {
buf->first = buf->last = chain; buf->first = buf->last = chain;
buf->previous_to_last = NULL; buf->last_with_data = chain;
} else { } else {
/* the last chain is empty so we can just drop it */ /* the last chain is empty so we can just drop it */
if (buf->last->off == 0 && !CHAIN_PINNED(buf->last)) { if (buf->last->off == 0 && !CHAIN_PINNED(buf->last)) {
if (buf->last_with_data == buf->last)
buf->last_with_data = chain;
evbuffer_chain_free(buf->last); evbuffer_chain_free(buf->last);
buf->previous_to_last->next = chain;
buf->last = chain; buf->last = chain;
} else { } else {
buf->previous_to_last = buf->last;
buf->last->next = chain; buf->last->next = chain;
buf->last = chain; buf->last = chain;
} }
if (chain->off)
buf->last_with_data = chain;
} }
buf->total_len += chain->off; buf->total_len += chain->off;
@ -520,9 +522,9 @@ evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
vec[0].iov_len = CHAIN_SPACE_LEN(chain); vec[0].iov_len = CHAIN_SPACE_LEN(chain);
n = 1; n = 1;
} else { } else {
if (_evbuffer_expand_fast(buf, size)<0) if (_evbuffer_expand_fast(buf, size, n_vecs)<0)
goto done; goto done;
n = _evbuffer_read_setup_vecs(buf, size, vec, &chain, 0); n = _evbuffer_read_setup_vecs(buf, size, vec, n_vecs, &chain, 0);
} }
done: done:
@ -531,50 +533,81 @@ done:
} }
static int
advance_last_with_data(struct evbuffer *buf)
{
int n = 0;
ASSERT_EVBUFFER_LOCKED(buf);
if (!buf->last_with_data)
return 0;
while (buf->last_with_data->next && buf->last_with_data->next->off) {
buf->last_with_data = buf->last_with_data->next;
++n;
}
return n;
}
int int
evbuffer_commit_space(struct evbuffer *buf, evbuffer_commit_space(struct evbuffer *buf,
struct evbuffer_iovec *vec, int n_vecs) struct evbuffer_iovec *vec, int n_vecs)
{ {
struct evbuffer_chain *last, *prev; struct evbuffer_chain *firstchain, *chain;
int result = -1; int result = -1;
size_t added; size_t added = 0;
int i;
EVBUFFER_LOCK(buf); EVBUFFER_LOCK(buf);
prev = buf->previous_to_last;
last = buf->last;
if (buf->freeze_end) if (buf->freeze_end)
goto done; goto done;
if (n_vecs < 1 || n_vecs > 2) if (n_vecs == 0) {
result = 0;
goto done; goto done;
if (n_vecs == 2) { } else if (n_vecs == 1 &&
if (!prev || !last || (buf->last && vec[0].iov_base == CHAIN_SPACE_PTR(buf->last))) {
vec[0].iov_base != CHAIN_SPACE_PTR(prev) || /* The user only got or used one chain; it might not
vec[1].iov_base != CHAIN_SPACE_PTR(last) || * be the first one with space in it. */
vec[0].iov_len > CHAIN_SPACE_LEN(prev) || if (vec[0].iov_len > CHAIN_SPACE_LEN(buf->last))
vec[1].iov_len > CHAIN_SPACE_LEN(last))
goto done; goto done;
buf->last->off += vec[0].iov_len;
prev->off += vec[0].iov_len;
last->off += vec[1].iov_len;
added = vec[0].iov_len + vec[1].iov_len;
} else {
/* n_vecs == 1 */
struct evbuffer_chain *chain;
if (prev && vec[0].iov_base == CHAIN_SPACE_PTR(prev))
chain = prev;
else if (last && vec[0].iov_base == CHAIN_SPACE_PTR(last))
chain = last;
else
goto done;
if (vec[0].iov_len > CHAIN_SPACE_LEN(chain))
goto done;
chain->off += vec[0].iov_len;
added = vec[0].iov_len; added = vec[0].iov_len;
if (added)
buf->last_with_data = buf->last;
goto okay;
} }
/* Advance 'firstchain' to the first chain with space in it. */
firstchain = buf->last_with_data;
if (!firstchain)
goto done;
if (CHAIN_SPACE_LEN(firstchain) == 0) {
firstchain = firstchain->next;
}
chain = firstchain;
/* pass 1: make sure that the pointers and lengths of vecs[] are in
* bounds before we try to commit anything. */
for (i=0; i<n_vecs; ++i) {
if (!chain)
goto done;
if (vec[i].iov_base != CHAIN_SPACE_PTR(chain) ||
vec[i].iov_len > CHAIN_SPACE_LEN(chain))
goto done;
chain = chain->next;
}
/* pass 2: actually adjust all the chains. */
chain = firstchain;
for (i=0; i<n_vecs; ++i) {
chain->off += vec[i].iov_len;
added += vec[i].iov_len;
if (vec[i].iov_len)
buf->last_with_data = chain;
chain = chain->next;
}
okay:
buf->total_len += added; buf->total_len += added;
buf->n_add_for_cb += added; buf->n_add_for_cb += added;
result = 0; result = 0;
@ -589,7 +622,7 @@ done:
ASSERT_EVBUFFER_LOCKED(dst); \ ASSERT_EVBUFFER_LOCKED(dst); \
(dst)->first = NULL; \ (dst)->first = NULL; \
(dst)->last = NULL; \ (dst)->last = NULL; \
(dst)->previous_to_last = NULL; \ (dst)->last_with_data = NULL; \
(dst)->total_len = 0; \ (dst)->total_len = 0; \
} while (0) } while (0)
@ -597,7 +630,7 @@ done:
ASSERT_EVBUFFER_LOCKED(dst); \ ASSERT_EVBUFFER_LOCKED(dst); \
ASSERT_EVBUFFER_LOCKED(src); \ ASSERT_EVBUFFER_LOCKED(src); \
(dst)->first = (src)->first; \ (dst)->first = (src)->first; \
(dst)->previous_to_last = (src)->previous_to_last; \ (dst)->last_with_data = (src)->last_with_data; \
(dst)->last = (src)->last; \ (dst)->last = (src)->last; \
(dst)->total_len = (src)->total_len; \ (dst)->total_len = (src)->total_len; \
} while (0) } while (0)
@ -606,8 +639,8 @@ done:
ASSERT_EVBUFFER_LOCKED(dst); \ ASSERT_EVBUFFER_LOCKED(dst); \
ASSERT_EVBUFFER_LOCKED(src); \ ASSERT_EVBUFFER_LOCKED(src); \
(dst)->last->next = (src)->first; \ (dst)->last->next = (src)->first; \
(dst)->previous_to_last = (src)->previous_to_last ? \ if ((src)->last_with_data) \
(src)->previous_to_last : (dst)->last; \ (dst)->last_with_data = (src)->last_with_data; \
(dst)->last = (src)->last; \ (dst)->last = (src)->last; \
(dst)->total_len += (src)->total_len; \ (dst)->total_len += (src)->total_len; \
} while (0) } while (0)
@ -618,11 +651,10 @@ done:
(src)->last->next = (dst)->first; \ (src)->last->next = (dst)->first; \
(dst)->first = (src)->first; \ (dst)->first = (src)->first; \
(dst)->total_len += (src)->total_len; \ (dst)->total_len += (src)->total_len; \
if ((dst)->previous_to_last == NULL) \ if ((dst)->last_with_data == NULL) \
(dst)->previous_to_last = (src)->last; \ (dst)->last_with_data = (src)->last_with_data; \
} while (0) } while (0)
int int
evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{ {
@ -734,6 +766,8 @@ evbuffer_drain(struct evbuffer *buf, size_t len)
for (chain = buf->first; len >= chain->off; chain = next) { for (chain = buf->first; len >= chain->off; chain = next) {
next = chain->next; next = chain->next;
len -= chain->off; len -= chain->off;
if (chain == buf->last_with_data)
buf->last_with_data = next;
if (len == 0 && CHAIN_PINNED_R(chain)) if (len == 0 && CHAIN_PINNED_R(chain))
break; break;
@ -741,8 +775,6 @@ evbuffer_drain(struct evbuffer *buf, size_t len)
} }
buf->first = chain; buf->first = chain;
if (buf->first == buf->last)
buf->previous_to_last = NULL;
chain->misalign += len; chain->misalign += len;
chain->off -= len; chain->off -= len;
} }
@ -789,6 +821,9 @@ evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)
data += chain->off; data += chain->off;
datlen -= chain->off; datlen -= chain->off;
if (chain == buf->last_with_data)
buf->last_with_data = chain->next;
tmp = chain; tmp = chain;
chain = chain->next; chain = chain->next;
evbuffer_chain_free(tmp); evbuffer_chain_free(tmp);
@ -797,8 +832,6 @@ evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)
buf->first = chain; buf->first = chain;
if (chain == NULL) if (chain == NULL)
buf->last = NULL; buf->last = NULL;
if (buf->first == buf->last)
buf->previous_to_last = NULL;
if (datlen) { if (datlen) {
memcpy(data, chain->buffer + chain->misalign, datlen); memcpy(data, chain->buffer + chain->misalign, datlen);
@ -827,7 +860,7 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
/*XXX We should have an option to force this to be zero-copy.*/ /*XXX We should have an option to force this to be zero-copy.*/
/*XXX can fail badly on sendfile case. */ /*XXX can fail badly on sendfile case. */
struct evbuffer_chain *chain, *previous, *previous_to_previous = NULL; struct evbuffer_chain *chain, *previous;
size_t nread = 0; size_t nread = 0;
int result; int result;
@ -855,9 +888,12 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
/* removes chains if possible */ /* removes chains if possible */
while (chain->off <= datlen) { while (chain->off <= datlen) {
/* We can't remove the last with data from src unless we
* remove all chains, in which case we would have done the if
* block above */
EVUTIL_ASSERT(chain != src->last_with_data);
nread += chain->off; nread += chain->off;
datlen -= chain->off; datlen -= chain->off;
previous_to_previous = previous;
previous = chain; previous = chain;
chain = chain->next; chain = chain->next;
} }
@ -869,12 +905,10 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
} else { } else {
dst->last->next = src->first; dst->last->next = src->first;
} }
dst->previous_to_last = previous_to_previous;
dst->last = previous; dst->last = previous;
dst->last_with_data = dst->last;
previous->next = NULL; previous->next = NULL;
src->first = chain; src->first = chain;
if (src->first == src->last)
src->previous_to_last = NULL;
dst->total_len += nread; dst->total_len += nread;
dst->n_add_for_cb += nread; dst->n_add_for_cb += nread;
@ -907,6 +941,7 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size)
struct evbuffer_chain *chain, *next, *tmp; struct evbuffer_chain *chain, *next, *tmp;
unsigned char *buffer, *result = NULL; unsigned char *buffer, *result = NULL;
ev_ssize_t remaining; ev_ssize_t remaining;
int removed_last_with_data = 0;
EVBUFFER_LOCK(buf); EVBUFFER_LOCK(buf);
@ -976,6 +1011,8 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size)
memcpy(buffer, chain->buffer + chain->misalign, chain->off); memcpy(buffer, chain->buffer + chain->misalign, chain->off);
size -= chain->off; size -= chain->off;
buffer += chain->off; buffer += chain->off;
if (chain == buf->last_with_data)
removed_last_with_data = 1;
evbuffer_chain_free(chain); evbuffer_chain_free(chain);
} }
@ -984,16 +1021,19 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size)
memcpy(buffer, chain->buffer + chain->misalign, size); memcpy(buffer, chain->buffer + chain->misalign, size);
chain->misalign += size; chain->misalign += size;
chain->off -= size; chain->off -= size;
if (chain == buf->last)
buf->previous_to_last = tmp;
} else { } else {
buf->last = tmp; buf->last = tmp;
/* the last is already the first, so we have no previous */
buf->previous_to_last = NULL;
} }
tmp->next = chain; tmp->next = chain;
if (removed_last_with_data) {
int n;
buf->last_with_data = buf->first;
n = advance_last_with_data(buf);
EVUTIL_ASSERT(n == 0);
}
result = (tmp->buffer + tmp->misalign); result = (tmp->buffer + tmp->misalign);
done: done:
@ -1380,8 +1420,11 @@ evbuffer_prepend(struct evbuffer *buf, const void *data, size_t datlen)
if ((tmp = evbuffer_chain_new(datlen)) == NULL) if ((tmp = evbuffer_chain_new(datlen)) == NULL)
goto done; goto done;
buf->first = tmp; buf->first = tmp;
if (buf->previous_to_last == NULL) if (buf->last_with_data == NULL)
buf->previous_to_last = tmp; buf->last_with_data = tmp;
else if (chain && buf->last_with_data == chain && 0==chain->off)
buf->last_with_data = tmp;
tmp->next = chain; tmp->next = chain;
tmp->off = datlen; tmp->off = datlen;
@ -1462,9 +1505,9 @@ evbuffer_expand(struct evbuffer *buf, size_t datlen)
/* fix up the chain */ /* fix up the chain */
if (buf->first == chain) if (buf->first == chain)
buf->first = tmp; buf->first = tmp;
if (buf->previous_to_last)
buf->previous_to_last->next = tmp;
buf->last = tmp; buf->last = tmp;
if (buf->last->off || buf->last_with_data == chain)
buf->last_with_data = tmp;
evbuffer_chain_free(chain); evbuffer_chain_free(chain);
@ -1475,17 +1518,21 @@ err:
return result; return result;
} }
/* Make sure that datlen bytes are available for writing in the last two /* Make sure that datlen bytes are available for writing in the last n
* chains. Never copies or moves data. */ * chains. Never copies or moves data. */
int int
_evbuffer_expand_fast(struct evbuffer *buf, size_t datlen) _evbuffer_expand_fast(struct evbuffer *buf, size_t datlen, int n)
{ {
struct evbuffer_chain *chain = buf->last, *tmp; struct evbuffer_chain *chain = buf->last, *tmp, *next;
size_t avail, avail_in_prev = 0; size_t avail;
int used;
ASSERT_EVBUFFER_LOCKED(buf); ASSERT_EVBUFFER_LOCKED(buf);
EVUTIL_ASSERT(n >= 2);
if (chain == NULL || (chain->flags & EVBUFFER_IMMUTABLE)) { if (chain == NULL || (chain->flags & EVBUFFER_IMMUTABLE)) {
/* There is no last chunk, or we can't touch the last chunk.
* Just add a new chunk. */
chain = evbuffer_chain_new(datlen); chain = evbuffer_chain_new(datlen);
if (chain == NULL) if (chain == NULL)
return (-1); return (-1);
@ -1494,56 +1541,89 @@ _evbuffer_expand_fast(struct evbuffer *buf, size_t datlen)
return (0); return (0);
} }
/* How many bytes can we stick at the end of chain? */ used = 0; /* number of chains we're using space in. */
avail = 0; /* how much space they have. */
if (chain->off) { /* How many bytes can we stick at the end of buffer as it is? Iterate
avail = chain->buffer_len - (chain->off + chain->misalign); * over the chains at the end of the buffer, tring to see how much
avail_in_prev = 0; * space we have in the first n. */
} else { for (chain = buf->last_with_data; chain; chain = chain->next) {
/* No data in chain; realign it. */ if (chain->off) {
chain->misalign = 0; size_t space = CHAIN_SPACE_LEN(chain);
avail = chain->buffer_len; EVUTIL_ASSERT(chain == buf->last_with_data);
/* Can we stick some data in the penultimate chain? */ if (space) {
if (buf->previous_to_last) { avail += space;
struct evbuffer_chain *prev = buf->previous_to_last; ++used;
avail_in_prev = CHAIN_SPACE_LEN(prev); }
} else {
/* No data in chain; realign it. */
chain->misalign = 0;
avail += chain->buffer_len;
++used;
} }
if (avail >= datlen) {
/* There is already enough space. Just return */
return (0);
}
if (used == n)
break;
} }
/* If we can fit all the data, then we don't have to do anything */ /* There wasn't enough space in the first n chains with space in
if (avail+avail_in_prev >= datlen) * them. Either add a new chain with enough space, or replace all
return (0); * empty chains with one that has enough space, depending on n. */
if (used < n) {
/* The loop ran off the end of the chains before it hit n
* chains; we can add another. */
EVUTIL_ASSERT(chain == NULL);
/* Otherwise, we need a bigger chunk. */ tmp = evbuffer_chain_new(datlen - avail);
if (chain->off == 0) {
/* If there are no bytes on this chain, free it and
replace it with a better one. */
/* XXX round up. */
tmp = evbuffer_chain_new(datlen-avail_in_prev);
if (tmp == NULL)
return -1;
/* XXX write functions to in new chains */
if (buf->first == chain)
buf->first = tmp;
if (buf->previous_to_last)
buf->previous_to_last->next = tmp;
buf->last = tmp;
evbuffer_chain_free(chain);
} else {
/* Add a new chunk big enough to hold what won't fit
* in chunk. */
/*XXX round this up. */
tmp = evbuffer_chain_new(datlen-avail);
if (tmp == NULL) if (tmp == NULL)
return (-1); return (-1);
buf->previous_to_last = chain; buf->last->next = tmp;
chain->next = tmp;
buf->last = tmp; buf->last = tmp;
} /* (we would only set last_with_data if we added the first
* chain. But if the buffer had no chains, we would have
* just allocated a new chain earlier) */
return (0);
} else {
/* Nuke _all_ the empty chains. */
int rmv_all = 0; /* True iff we removed last_with_data. */
chain = buf->last_with_data;
if (!chain->off) {
EVUTIL_ASSERT(chain == buf->first);
rmv_all = 1;
avail = 0;
} else {
avail = CHAIN_SPACE_LEN(chain);
chain = chain->next;
}
return (0);
for (; chain; chain = next) {
next = chain->next;
EVUTIL_ASSERT(chain->off == 0);
evbuffer_chain_free(chain);
}
tmp = evbuffer_chain_new(datlen - avail);
if (tmp == NULL) {
if (rmv_all) {
ZERO_CHAIN(buf);
} else {
buf->last = buf->last_with_data;
buf->last_with_data->next = NULL;
}
return (-1);
}
if (rmv_all) {
buf->first = buf->last = buf->last_with_data = tmp;
} else {
buf->last_with_data->next = tmp;
buf->last = tmp;
}
return (0);
}
} }
/* /*
@ -1559,17 +1639,18 @@ _evbuffer_expand_fast(struct evbuffer *buf, size_t datlen)
#ifdef _EVENT_HAVE_SYS_UIO_H #ifdef _EVENT_HAVE_SYS_UIO_H
/* number of iovec we use for writev, fragmentation is going to determine /* number of iovec we use for writev, fragmentation is going to determine
* how much we end up writing */ * how much we end up writing */
#define NUM_IOVEC 128 #define NUM_WRITE_IOVEC 128
#define IOV_TYPE struct iovec #define IOV_TYPE struct iovec
#define IOV_PTR_FIELD iov_base #define IOV_PTR_FIELD iov_base
#define IOV_LEN_FIELD iov_len #define IOV_LEN_FIELD iov_len
#else #else
#define NUM_IOVEC 16 #define NUM_WRITE_IOVEC 16
#define IOV_TYPE WSABUF #define IOV_TYPE WSABUF
#define IOV_PTR_FIELD buf #define IOV_PTR_FIELD buf
#define IOV_LEN_FIELD len #define IOV_LEN_FIELD len
#endif #endif
#endif #endif
#define NUM_READ_IOVEC 4
#define EVBUFFER_MAX_READ 4096 #define EVBUFFER_MAX_READ 4096
@ -1578,7 +1659,8 @@ _evbuffer_expand_fast(struct evbuffer *buf, size_t datlen)
@param buf The buffer to read into @param buf The buffer to read into
@param howmuch How much we want to read. @param howmuch How much we want to read.
@param vecs An array of two iovecs or WSABUFs. @param vecs An array of two or more iovecs or WSABUFs.
@param n_vecs_avail The length of vecs
@param chainp A pointer to a variable to hold the first chain we're @param chainp A pointer to a variable to hold the first chain we're
reading into. reading into.
@param exact Boolean: if true, we do not provide more than 'howmuch' @param exact Boolean: if true, we do not provide more than 'howmuch'
@ -1587,52 +1669,36 @@ _evbuffer_expand_fast(struct evbuffer *buf, size_t datlen)
*/ */
int int
_evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch, _evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch,
struct evbuffer_iovec *vecs, struct evbuffer_chain **chainp, int exact) struct evbuffer_iovec *vecs, int n_vecs_avail,
struct evbuffer_chain **chainp, int exact)
{ {
struct evbuffer_chain *chain; struct evbuffer_chain *chain, *firstchain;
int nvecs; size_t so_far;
int i;
ASSERT_EVBUFFER_LOCKED(buf);
if (howmuch < 0) if (howmuch < 0)
return -1; return -1;
chain = buf->last; so_far = 0;
/* Let firstchain be the first chain with any space on it */
firstchain = buf->last_with_data;
if (CHAIN_SPACE_LEN(firstchain) == 0)
firstchain = firstchain->next;
if (chain->off == 0 && buf->previous_to_last && chain = firstchain;
CHAIN_SPACE_LEN(buf->previous_to_last)) { for (i = 0; i < n_vecs_avail && so_far < howmuch; ++i) {
/* The last chain is empty, so it's safe to size_t avail = CHAIN_SPACE_LEN(chain);
use the space in the next-to-last chain. if (avail > howmuch && exact)
*/ avail = howmuch;
struct evbuffer_chain *prev = buf->previous_to_last; vecs[i].iov_base = CHAIN_SPACE_PTR(chain);
vecs[0].iov_base = CHAIN_SPACE_PTR(prev); vecs[i].iov_len = avail;
vecs[0].iov_len = CHAIN_SPACE_LEN(prev); so_far += avail;
vecs[1].iov_base = CHAIN_SPACE_PTR(chain); chain = chain->next;
vecs[1].iov_len = CHAIN_SPACE_LEN(chain);
if (vecs[0].iov_len >= (size_t)howmuch) {
/* The next-to-last chain has enough
* space on its own. */
chain = prev;
nvecs = 1;
} else {
/* We'll need both chains. */
chain = prev;
nvecs = 2;
if (exact &&
(vecs[0].iov_len + vecs[1].iov_len > (size_t)howmuch)) {
vecs[1].iov_len = howmuch - vecs[0].iov_len;
}
}
} else {
/* There's data in the last chain, so we're
* not allowed to use the next-to-last. */
nvecs = 1;
vecs[0].iov_base = CHAIN_SPACE_PTR(chain);
vecs[0].iov_len = CHAIN_SPACE_LEN(chain);
if (exact && (vecs[0].iov_len > (size_t)howmuch))
vecs[0].iov_len = howmuch;
} }
*chainp = chain; *chainp = firstchain;
return nvecs; return i;
} }
/* TODO(niels): should this function return ev_ssize_t and take ev_ssize_t /* TODO(niels): should this function return ev_ssize_t and take ev_ssize_t
@ -1645,7 +1711,7 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
int result; int result;
#ifdef USE_IOVEC_IMPL #ifdef USE_IOVEC_IMPL
int nvecs; int nvecs, i, remaining;
#else #else
unsigned char *p; unsigned char *p;
#endif #endif
@ -1688,28 +1754,24 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
#ifdef USE_IOVEC_IMPL #ifdef USE_IOVEC_IMPL
/* Since we can use iovecs, we're willing to use the last /* Since we can use iovecs, we're willing to use the last
* _two_ chains. */ * NUM_READ_IOVEC chains. */
if (_evbuffer_expand_fast(buf, howmuch) == -1) { if (_evbuffer_expand_fast(buf, howmuch, NUM_READ_IOVEC) == -1) {
result = -1; result = -1;
goto done; goto done;
} else { } else {
IOV_TYPE vecs[2]; IOV_TYPE vecs[NUM_READ_IOVEC];
#ifdef _EVBUFFER_IOVEC_IS_NATIVE #ifdef _EVBUFFER_IOVEC_IS_NATIVE
nvecs = _evbuffer_read_setup_vecs(buf, howmuch, vecs, nvecs = _evbuffer_read_setup_vecs(buf, howmuch, vecs,
&chain, 1); NUM_READ_IOVEC, &chain, 1);
#else #else
/* We aren't using the native struct iovec. Therefore, /* We aren't using the native struct iovec. Therefore,
we are on win32. */ we are on win32. */
struct evbuffer_iovec ev_vecs[2]; struct evbuffer_iovec ev_vecs[NUM_READ_IOVEC];
nvecs = _evbuffer_read_setup_vecs(buf, howmuch, ev_vecs, nvecs = _evbuffer_read_setup_vecs(buf, howmuch, ev_vecs, 2,
&chain, 1); &chain, 1);
if (nvecs == 2) { for (i=0; i < nvecs; ++i)
WSABUF_FROM_EVBUFFER_IOV(&vecs[1], &ev_vecs[1]); WSABUF_FROM_EVBUFFER_IOV(&vecs[i], &ev_vecs[i]);
WSABUF_FROM_EVBUFFER_IOV(&vecs[0], &ev_vecs[0]);
} else if (nvecs == 1) {
WSABUF_FROM_EVBUFFER_IOV(&vecs[0], &ev_vecs[0]);
}
#endif #endif
#ifdef WIN32 #ifdef WIN32
@ -1762,19 +1824,22 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
} }
#ifdef USE_IOVEC_IMPL #ifdef USE_IOVEC_IMPL
if (nvecs == 2) { remaining = n;
for (i=0; i < nvecs; ++i) {
ev_ssize_t space = CHAIN_SPACE_LEN(chain); ev_ssize_t space = CHAIN_SPACE_LEN(chain);
if (space < n) { if (space < remaining) {
chain->off += space; chain->off += space;
chain->next->off += n-space; remaining -= space;
} else { } else {
chain->off += n; chain->off += remaining;
buf->last_with_data = chain;
break;
} }
} else { chain = chain->next;
chain->off += n;
} }
#else #else
chain->off += n; chain->off += n;
buf->last_with_data = chain;
#endif #endif
buf->total_len += n; buf->total_len += n;
buf->n_add_for_cb += n; buf->n_add_for_cb += n;
@ -1792,7 +1857,7 @@ static inline int
evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd, evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
ev_ssize_t howmuch) ev_ssize_t howmuch)
{ {
IOV_TYPE iov[NUM_IOVEC]; IOV_TYPE iov[NUM_WRITE_IOVEC];
struct evbuffer_chain *chain = buffer->first; struct evbuffer_chain *chain = buffer->first;
int n, i = 0; int n, i = 0;
@ -1803,7 +1868,7 @@ evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
/* XXX make this top out at some maximal data length? if the /* XXX make this top out at some maximal data length? if the
* buffer has (say) 1MB in it, split over 128 chains, there's * buffer has (say) 1MB in it, split over 128 chains, there's
* no way it all gets written in one go. */ * no way it all gets written in one go. */
while (chain != NULL && i < NUM_IOVEC && howmuch) { while (chain != NULL && i < NUM_WRITE_IOVEC && howmuch) {
#ifdef USE_SENDFILE #ifdef USE_SENDFILE
/* we cannot write the file info via writev */ /* we cannot write the file info via writev */
if (chain->flags & EVBUFFER_SENDFILE) if (chain->flags & EVBUFFER_SENDFILE)

View File

@ -249,12 +249,15 @@ evbuffer_launch_read(struct evbuffer *buf, size_t at_most,
buf_o->n_buffers = 0; buf_o->n_buffers = 0;
memset(buf_o->buffers, 0, sizeof(buf_o->buffers)); memset(buf_o->buffers, 0, sizeof(buf_o->buffers));
if (_evbuffer_expand_fast(buf, at_most) == -1) if (_evbuffer_expand_fast(buf, at_most, 2) == -1)
goto done; goto done;
evbuffer_freeze(buf, 0); evbuffer_freeze(buf, 0);
/* XXX This and evbuffer_read_setup_vecs() should say MAX_WSABUFS,
* not "2". But commit_read() above can't handle more than two
* buffers yet. */
nvecs = _evbuffer_read_setup_vecs(buf, at_most, nvecs = _evbuffer_read_setup_vecs(buf, at_most,
vecs, &chain, 1); vecs, 2, &chain, 1);
for (i=0;i<nvecs;++i) { for (i=0;i<nvecs;++i) {
WSABUF_FROM_EVBUFFER_IOV( WSABUF_FROM_EVBUFFER_IOV(
&buf_o->buffers[i], &buf_o->buffers[i],

View File

@ -73,16 +73,11 @@ struct evbuffer {
struct evbuffer_chain *first; struct evbuffer_chain *first;
/** The last chain in this buffer's linked list of chains. */ /** The last chain in this buffer's linked list of chains. */
struct evbuffer_chain *last; struct evbuffer_chain *last;
/** The next-to-last chain in this buffer's linked list of chains.
* NULL if the buffer has 0 or 1 chains. Used in case there's an /** The last chain that has any data in it. If all chains in the
* ongoing read that needs to be split across multiple chains: we want * buffer are empty, points to the first chain. If the buffer has no
* to add a new chain as a read target, but we don't want to lose our * chains, this is NULL. */
* pointer to the next-to-last chain if the read turns out to be struct evbuffer_chain *last_with_data;
* incomplete.
*/
/* FIXME: This should probably be called last_with_space and
* repurposed accordingly. */
struct evbuffer_chain *previous_to_last;
/** Total amount of bytes stored in all chains.*/ /** Total amount of bytes stored in all chains.*/
size_t total_len; size_t total_len;
@ -234,8 +229,8 @@ void _evbuffer_chain_unpin(struct evbuffer_chain *chain, unsigned flag);
void _evbuffer_decref_and_unlock(struct evbuffer *buffer); void _evbuffer_decref_and_unlock(struct evbuffer *buffer);
/** As evbuffer_expand, but does not guarantee that the newly allocated memory /** As evbuffer_expand, but does not guarantee that the newly allocated memory
* is contiguous. Instead, it may be split across two chunks. */ * is contiguous. Instead, it may be split across two or more chunks. */
int _evbuffer_expand_fast(struct evbuffer *, size_t); int _evbuffer_expand_fast(struct evbuffer *, size_t, int);
/** Helper: prepares for a readv/WSARecv call by expanding the buffer to /** Helper: prepares for a readv/WSARecv call by expanding the buffer to
* hold enough memory to read 'howmuch' bytes in possibly noncontiguous memory. * hold enough memory to read 'howmuch' bytes in possibly noncontiguous memory.
@ -244,7 +239,7 @@ int _evbuffer_expand_fast(struct evbuffer *, size_t);
* Returns the number of vecs used. * Returns the number of vecs used.
*/ */
int _evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch, int _evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch,
struct evbuffer_iovec *vecs, struct evbuffer_chain **chainp, int exact); struct evbuffer_iovec *vecs, int n_vecs, struct evbuffer_chain **chainp, int exact);
/* Helper macro: copies an evbuffer_iovec in ei to a win32 WSABUF in i. */ /* Helper macro: copies an evbuffer_iovec in ei to a win32 WSABUF in i. */
#define WSABUF_FROM_EVBUFFER_IOV(i,ei) do { \ #define WSABUF_FROM_EVBUFFER_IOV(i,ei) do { \

View File

@ -66,31 +66,44 @@
static int static int
_evbuffer_validate(struct evbuffer *buf) _evbuffer_validate(struct evbuffer *buf)
{ {
struct evbuffer_chain *chain, *previous = NULL; struct evbuffer_chain *chain;
size_t sum = 0; size_t sum = 0;
int found_last_with_data = 0;
if (buf->first == NULL) { if (buf->first == NULL) {
tt_assert(buf->last == NULL); tt_assert(buf->last == NULL);
tt_assert(buf->previous_to_last == NULL);
tt_assert(buf->total_len == 0); tt_assert(buf->total_len == 0);
} }
if (buf->previous_to_last == NULL) {
tt_assert(buf->first == buf->last);
}
chain = buf->first; chain = buf->first;
while (chain != NULL) { while (chain != NULL) {
if (chain == buf->last_with_data)
found_last_with_data = 1;
sum += chain->off; sum += chain->off;
if (chain->next == NULL) { if (chain->next == NULL) {
tt_assert(buf->previous_to_last == previous);
tt_assert(buf->last == chain); tt_assert(buf->last == chain);
} }
tt_assert(chain->buffer_len >= chain->misalign + chain->off); tt_assert(chain->buffer_len >= chain->misalign + chain->off);
previous = chain;
chain = chain->next; chain = chain->next;
} }
if (buf->first)
tt_assert(buf->last_with_data);
if (buf->last_with_data) {
tt_assert(found_last_with_data);
chain = buf->last_with_data;
if (chain->off == 0 || buf->total_len == 0) {
tt_assert(chain->off == 0)
tt_assert(chain == buf->first);
tt_assert(buf->total_len == 0);
}
chain = chain->next;
while (chain != NULL) {
tt_assert(chain->off == 0);
chain = chain->next;
}
}
tt_assert(sum == buf->total_len); tt_assert(sum == buf->total_len);
return 1; return 1;
end: end:
@ -239,8 +252,10 @@ test_evbuffer_reserve2(void *ptr)
cp = v[0].iov_base; cp = v[0].iov_base;
remaining = v[0].iov_len - 512; remaining = v[0].iov_len - 512;
v[0].iov_len = 512; v[0].iov_len = 512;
evbuffer_validate(buf);
tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1));
tt_int_op(evbuffer_get_length(buf), ==, 512); tt_int_op(evbuffer_get_length(buf), ==, 512);
evbuffer_validate(buf);
/* Ask for another same-chunk request, in an existing chunk. Use 8 /* Ask for another same-chunk request, in an existing chunk. Use 8
* bytes of it. */ * bytes of it. */
@ -253,6 +268,7 @@ test_evbuffer_reserve2(void *ptr)
tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1));
tt_int_op(evbuffer_get_length(buf), ==, 520); tt_int_op(evbuffer_get_length(buf), ==, 520);
remaining -= 8; remaining -= 8;
evbuffer_validate(buf);
/* Now ask for a request that will be split. Use only one byte of it, /* Now ask for a request that will be split. Use only one byte of it,
though. */ though. */
@ -268,10 +284,12 @@ test_evbuffer_reserve2(void *ptr)
tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1));
tt_int_op(evbuffer_get_length(buf), ==, 521); tt_int_op(evbuffer_get_length(buf), ==, 521);
remaining -= 1; remaining -= 1;
evbuffer_validate(buf);
/* Now ask for a request that will be split. Use some of the first /* Now ask for a request that will be split. Use some of the first
* part and some of the second. */ * part and some of the second. */
n = evbuffer_reserve_space(buf, remaining+64, v, 2); n = evbuffer_reserve_space(buf, remaining+64, v, 2);
evbuffer_validate(buf);
tt_int_op(n, ==, 2); tt_int_op(n, ==, 2);
tt_assert(cp + 521 == v[0].iov_base); tt_assert(cp + 521 == v[0].iov_base);
tt_int_op(remaining, ==, v[0].iov_len); tt_int_op(remaining, ==, v[0].iov_len);
@ -283,7 +301,7 @@ test_evbuffer_reserve2(void *ptr)
v[1].iov_len = 60; v[1].iov_len = 60;
tt_int_op(0, ==, evbuffer_commit_space(buf, v, 2)); tt_int_op(0, ==, evbuffer_commit_space(buf, v, 2));
tt_int_op(evbuffer_get_length(buf), ==, 981); tt_int_op(evbuffer_get_length(buf), ==, 981);
evbuffer_validate(buf);
/* Now peek to make sure stuff got made how we like. */ /* Now peek to make sure stuff got made how we like. */
memset(v,0,sizeof(v)); memset(v,0,sizeof(v));
@ -310,6 +328,80 @@ end:
evbuffer_free(buf); evbuffer_free(buf);
} }
static void
test_evbuffer_reserve_many(void *ptr)
{
/* This is a glass-box test to handle expanding a buffer with more
* chunks and reallocating chunks as needed */
struct evbuffer *buf = evbuffer_new();
struct evbuffer_iovec v[8];
int n;
size_t sz;
int add_data = ptr && !strcmp(ptr, "add");
int fill_first = ptr && !strcmp(ptr, "fill");
char *cp1, *cp2;
/* When reserving the the first chunk, we just allocate it */
n = evbuffer_reserve_space(buf, 128, v, 2);
evbuffer_validate(buf);
tt_int_op(n, ==, 1);
tt_assert(v[0].iov_len >= 128);
sz = v[0].iov_len;
cp1 = v[0].iov_base;
if (add_data) {
*(char*)v[0].iov_base = 'X';
v[0].iov_len = 1;
n = evbuffer_commit_space(buf, v, 1);
tt_int_op(n, ==, 0);
} else if (fill_first) {
memset(v[0].iov_base, 'X', v[0].iov_len);
n = evbuffer_commit_space(buf, v, 1);
tt_int_op(n, ==, 0);
n = evbuffer_reserve_space(buf, 128, v, 2);
tt_int_op(n, ==, 1);
sz = v[0].iov_len;
tt_assert(v[0].iov_base != cp1);
cp1 = v[0].iov_base;
}
/* Make another chunk get added. */
n = evbuffer_reserve_space(buf, sz+128, v, 2);
evbuffer_validate(buf);
tt_int_op(n, ==, 2);
sz = v[0].iov_len + v[1].iov_len;
tt_int_op(sz, >=, v[0].iov_len+128);
if (add_data) {
tt_assert(v[0].iov_base == cp1 + 1);
} else {
tt_assert(v[0].iov_base == cp1);
}
cp1 = v[0].iov_base;
cp2 = v[1].iov_base;
/* And a third chunk. */
n = evbuffer_reserve_space(buf, sz+128, v, 3);
evbuffer_validate(buf);
tt_int_op(n, ==, 3);
tt_assert(cp1 == v[0].iov_base);
tt_assert(cp2 == v[1].iov_base);
sz = v[0].iov_len + v[1].iov_len + v[2].iov_len;
/* Now force a reallocation by asking for more space in only 2
* buffers. */
n = evbuffer_reserve_space(buf, sz+128, v, 2);
evbuffer_validate(buf);
if (add_data) {
tt_int_op(n, ==, 2);
tt_assert(cp1 == v[0].iov_base);
} else {
tt_int_op(n, ==, 1);
}
end:
evbuffer_free(buf);
}
static int reference_cb_called; static int reference_cb_called;
static void static void
reference_cb(const void *data, size_t len, void *extra) reference_cb(const void *data, size_t len, void *extra)
@ -358,6 +450,7 @@ test_evbuffer_reference(void *ptr)
tt_assert(!memcmp(evbuffer_pullup(dst, strlen(data)), tt_assert(!memcmp(evbuffer_pullup(dst, strlen(data)),
data, strlen(data))); data, strlen(data)));
evbuffer_validate(dst);
end: end:
evbuffer_free(dst); evbuffer_free(dst);
@ -394,6 +487,7 @@ test_evbuffer_add_file(void *ptr)
if (memcmp(compare, data, strlen(data))) if (memcmp(compare, data, strlen(data)))
tt_abort_msg("Data from add_file differs."); tt_abort_msg("Data from add_file differs.");
evbuffer_validate(src);
end: end:
EVUTIL_CLOSESOCKET(pair[0]); EVUTIL_CLOSESOCKET(pair[0]);
EVUTIL_CLOSESOCKET(pair[1]); EVUTIL_CLOSESOCKET(pair[1]);
@ -711,6 +805,7 @@ test_evbuffer_ptr_set(void *ptr)
v[0].iov_len = 5000; v[0].iov_len = 5000;
memset(v[0].iov_base, 1, v[0].iov_len); memset(v[0].iov_base, 1, v[0].iov_len);
evbuffer_commit_space(buf, v, 1); evbuffer_commit_space(buf, v, 1);
evbuffer_validate(buf);
evbuffer_reserve_space(buf, 4000, v, 1); evbuffer_reserve_space(buf, 4000, v, 1);
v[0].iov_len = 4000; v[0].iov_len = 4000;
@ -721,6 +816,7 @@ test_evbuffer_ptr_set(void *ptr)
v[0].iov_len = 3000; v[0].iov_len = 3000;
memset(v[0].iov_base, 3, v[0].iov_len); memset(v[0].iov_base, 3, v[0].iov_len);
evbuffer_commit_space(buf, v, 1); evbuffer_commit_space(buf, v, 1);
evbuffer_validate(buf);
tt_int_op(evbuffer_get_length(buf), ==, 12000); tt_int_op(evbuffer_get_length(buf), ==, 12000);
@ -867,7 +963,9 @@ test_evbuffer_callbacks(void *ptr)
evbuffer_add_printf(buf, "This will not."); evbuffer_add_printf(buf, "This will not.");
tt_str_op(evbuffer_pullup(buf, -1), ==, "This will not."); tt_str_op(evbuffer_pullup(buf, -1), ==, "This will not.");
evbuffer_validate(buf);
evbuffer_drain(buf, evbuffer_get_length(buf)); evbuffer_drain(buf, evbuffer_get_length(buf));
evbuffer_validate(buf);
#if 0 #if 0
/* Now let's try a suspended callback. */ /* Now let's try a suspended callback. */
@ -938,6 +1036,7 @@ test_evbuffer_add_reference(void *ptr)
/* Make sure that prepending does not meddle with immutable data */ /* Make sure that prepending does not meddle with immutable data */
tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0); tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0);
tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); tt_int_op(memcmp(chunk1, "If you", 6), ==, 0);
evbuffer_validate(buf1);
/* Make sure that when the chunk is over, the callback is invoked. */ /* Make sure that when the chunk is over, the callback is invoked. */
evbuffer_drain(buf1, 7); /* Remove prepended stuff. */ evbuffer_drain(buf1, 7); /* Remove prepended stuff. */
@ -949,6 +1048,7 @@ test_evbuffer_add_reference(void *ptr)
tt_assert(ref_done_cb_called_with_data == chunk1); tt_assert(ref_done_cb_called_with_data == chunk1);
tt_assert(ref_done_cb_called_with_len == len1); tt_assert(ref_done_cb_called_with_len == len1);
tt_int_op(ref_done_cb_called_count, ==, 1); tt_int_op(ref_done_cb_called_count, ==, 1);
evbuffer_validate(buf1);
/* Drain some of the remaining chunk, then add it to another buffer */ /* Drain some of the remaining chunk, then add it to another buffer */
evbuffer_drain(buf1, 6); /* Remove the ", you ". */ evbuffer_drain(buf1, 6); /* Remove the ", you ". */
@ -964,6 +1064,7 @@ test_evbuffer_add_reference(void *ptr)
evbuffer_drain(buf2, evbuffer_get_length(buf2)); evbuffer_drain(buf2, evbuffer_get_length(buf2));
tt_int_op(ref_done_cb_called_count, ==, 2); tt_int_op(ref_done_cb_called_count, ==, 2);
tt_assert(ref_done_cb_called_with == (void*)222); tt_assert(ref_done_cb_called_with == (void*)222);
evbuffer_validate(buf2);
/* Now add more stuff to buf1 and make sure that it gets removed on /* Now add more stuff to buf1 and make sure that it gets removed on
* free. */ * free. */
@ -1250,6 +1351,9 @@ static const struct testcase_setup_t nil_setup = {
struct testcase_t evbuffer_testcases[] = { struct testcase_t evbuffer_testcases[] = {
{ "evbuffer", test_evbuffer, 0, NULL, NULL }, { "evbuffer", test_evbuffer, 0, NULL, NULL },
{ "reserve2", test_evbuffer_reserve2, 0, NULL, NULL }, { "reserve2", test_evbuffer_reserve2, 0, NULL, NULL },
{ "reserve_many", test_evbuffer_reserve_many, 0, NULL, NULL },
{ "reserve_many2", test_evbuffer_reserve_many, 0, &nil_setup, (void*)"add" },
{ "reserve_many3", test_evbuffer_reserve_many, 0, &nil_setup, (void*)"fill" },
{ "reference", test_evbuffer_reference, 0, NULL, NULL }, { "reference", test_evbuffer_reference, 0, NULL, NULL },
{ "iterative", test_evbuffer_iterative, 0, NULL, NULL }, { "iterative", test_evbuffer_iterative, 0, NULL, NULL },
{ "readln", test_evbuffer_readln, TT_NO_LOGS, &basic_setup, NULL }, { "readln", test_evbuffer_readln, TT_NO_LOGS, &basic_setup, NULL },