Merge remote-tracking branch 'fancycode/buffer_references'

Conflicts:
	buffer.c
This commit is contained in:
Nick Mathewson 2011-12-08 14:04:04 -05:00
commit de5428e7d5
4 changed files with 255 additions and 2 deletions

120
buffer.c
View File

@ -150,7 +150,7 @@ static struct evbuffer_chain *evbuffer_expand_singlechain(struct evbuffer *buf,
static int evbuffer_ptr_subtract(struct evbuffer *buf, struct evbuffer_ptr *pos,
size_t howfar);
static int evbuffer_file_segment_materialize(struct evbuffer_file_segment *seg);
static inline void evbuffer_chain_incref(struct evbuffer_chain *chain);
static struct evbuffer_chain *
evbuffer_chain_new(size_t size)
@ -178,17 +178,29 @@ evbuffer_chain_new(size_t size)
*/
chain->buffer = EVBUFFER_CHAIN_EXTRA(u_char, chain);
chain->refcnt = 1;
return (chain);
}
static inline void
evbuffer_chain_free(struct evbuffer_chain *chain)
{
EVUTIL_ASSERT(chain->refcnt > 0);
if (--chain->refcnt > 0) {
// chain is still referenced by other chains
return;
}
if (CHAIN_PINNED(chain)) {
// will get freed once no longer dangling
chain->refcnt++;
chain->flags |= EVBUFFER_DANGLING;
return;
}
// safe to release chain, it's either a referencing
// chain or all references to it have been freed
if (chain->flags & EVBUFFER_REFERENCE) {
struct evbuffer_chain_reference *info =
EVBUFFER_CHAIN_EXTRA(
@ -212,6 +224,21 @@ evbuffer_chain_free(struct evbuffer_chain *chain)
evbuffer_file_segment_free(info->segment);
}
}
if (chain->flags & EVBUFFER_MULTICAST) {
struct evbuffer_multicast_parent *info =
EVBUFFER_CHAIN_EXTRA(
struct evbuffer_multicast_parent,
chain);
// referencing chain is being freed, decrease
// refcounts of source chain and associated
// evbuffer (which get freed once both reach
// zero)
EVUTIL_ASSERT(info->source != NULL);
EVUTIL_ASSERT(info->parent != NULL);
EVBUFFER_LOCK(info->source);
evbuffer_chain_free(info->parent);
_evbuffer_decref_and_unlock(info->source);
}
mm_free(chain);
}
@ -316,6 +343,12 @@ _evbuffer_chain_unpin(struct evbuffer_chain *chain, unsigned flag)
evbuffer_chain_free(chain);
}
static inline void
evbuffer_chain_incref(struct evbuffer_chain *chain)
{
++chain->refcnt;
}
struct evbuffer *
evbuffer_new(void)
{
@ -853,6 +886,46 @@ APPEND_CHAIN(struct evbuffer *dst, struct evbuffer *src)
dst->total_len += src->total_len;
}
static inline void
APPEND_CHAIN_MULTICAST(struct evbuffer *dst, struct evbuffer *src)
{
struct evbuffer_chain *tmp;
struct evbuffer_chain *chain = src->first;
struct evbuffer_multicast_parent *extra;
ASSERT_EVBUFFER_LOCKED(dst);
ASSERT_EVBUFFER_LOCKED(src);
for (; chain; chain = chain->next) {
if (!chain->off || chain->flags & EVBUFFER_DANGLING) {
// skip empty chains
continue;
}
tmp = evbuffer_chain_new(sizeof(struct evbuffer_multicast_parent));
if (!tmp) {
event_warn("%s: out of memory", __func__);
return;
}
extra = EVBUFFER_CHAIN_EXTRA(struct evbuffer_multicast_parent, tmp);
// reference evbuffer containing source chain so it
// doesn't get released while the chain is still
// being referenced to
_evbuffer_incref(src);
extra->source = src;
// reference source chain which now becomes immutable
evbuffer_chain_incref(chain);
extra->parent = chain;
chain->flags |= EVBUFFER_IMMUTABLE;
tmp->buffer_len = chain->buffer_len;
tmp->misalign = chain->misalign;
tmp->off = chain->off;
tmp->flags |= EVBUFFER_MULTICAST|EVBUFFER_IMMUTABLE;
tmp->buffer = chain->buffer;
evbuffer_chain_insert(dst, tmp);
}
}
static void
PREPEND_CHAIN(struct evbuffer *dst, struct evbuffer *src)
{
@ -917,6 +990,49 @@ done:
return result;
}
int
evbuffer_add_buffer_reference(struct evbuffer *outbuf, struct evbuffer *inbuf)
{
size_t in_total_len, out_total_len;
struct evbuffer_chain *chain;
int result = 0;
EVBUFFER_LOCK2(inbuf, outbuf);
in_total_len = inbuf->total_len;
out_total_len = outbuf->total_len;
chain = inbuf->first;
if (in_total_len == 0)
goto done;
if (outbuf->freeze_end || outbuf == inbuf) {
result = -1;
goto done;
}
for (; chain; chain = chain->next) {
if ((chain->flags & (EVBUFFER_FILESEGMENT|EVBUFFER_SENDFILE|EVBUFFER_MULTICAST)) != 0) {
// chain type can not be referenced
result = -1;
goto done;
}
}
if (out_total_len == 0) {
/* There might be an empty chain at the start of outbuf; free
* it. */
evbuffer_free_all_chains(outbuf->first);
}
APPEND_CHAIN_MULTICAST(outbuf, inbuf);
outbuf->n_add_for_cb += in_total_len;
evbuffer_invoke_callbacks(outbuf);
done:
EVBUFFER_UNLOCK2(inbuf, outbuf);
return result;
}
int
evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{

View File

@ -185,6 +185,11 @@ struct evbuffer_chain {
/** a chain that should be freed, but can't be freed until it is
* un-pinned. */
#define EVBUFFER_DANGLING 0x0040
/** a chain that is a referenced copy of another chain */
#define EVBUFFER_MULTICAST 0x0080
/** number of references to this chain */
int refcnt;
/** Usually points to the read-write memory belonging to this
* buffer allocated as part of the evbuffer_chain allocation.
@ -243,6 +248,15 @@ struct evbuffer_file_segment {
ev_off_t length;
};
/** Information about the multicast parent of a chain. Lives at the
* end of an evbuffer_chain with the EVBUFFER_MULTICAST flag set. */
struct evbuffer_multicast_parent {
/** source buffer the multicast parent belongs to */
struct evbuffer *source;
/** multicast parent for this chain */
struct evbuffer_chain *parent;
};
#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)

View File

@ -437,6 +437,22 @@ char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out,
*/
int evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf);
/**
Copy data from one evbuffer into another evbuffer.
This is a non-destructive add. The data from one buffer is copied
into the other buffer. However, no unnecessary memory copies occur.
Note that buffers already containing buffer references can't be added
to other buffers.
@param outbuf the output buffer
@param inbuf the input buffer
@return 0 if successful, or -1 if an error occurred
*/
int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
struct evbuffer *inbuf);
/**
A cleanup function for a piece of memory added to an evbuffer by
reference.

View File

@ -1596,6 +1596,111 @@ end:
evbuffer_free(buf2);
}
static void
test_evbuffer_multicast(void *ptr)
{
const char chunk1[] = "If you have found the answer to such a problem";
const char chunk2[] = "you ought to write it up for publication";
/* -- Knuth's "Notes on the Exercises" from TAOCP */
char tmp[16];
size_t len1 = strlen(chunk1), len2=strlen(chunk2);
struct evbuffer *buf1 = NULL, *buf2 = NULL;
buf1 = evbuffer_new();
tt_assert(buf1);
evbuffer_add(buf1, chunk1, len1);
evbuffer_add(buf1, ", ", 2);
evbuffer_add(buf1, chunk2, len2);
tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2);
buf2 = evbuffer_new();
tt_assert(buf2);
tt_int_op(evbuffer_add_buffer_reference(buf2, buf1), ==, 0);
// nested references are not allowed
tt_int_op(evbuffer_add_buffer_reference(buf2, buf2), ==, -1);
tt_int_op(evbuffer_add_buffer_reference(buf1, buf2), ==, -1);
// both buffers contain the same amount of data
tt_int_op(evbuffer_get_length(buf1), ==, evbuffer_get_length(buf1));
/* Make sure we can drain a little from the first buffer. */
tt_int_op(evbuffer_remove(buf1, tmp, 6), ==, 6);
tt_int_op(memcmp(tmp, "If you", 6), ==, 0);
tt_int_op(evbuffer_remove(buf1, tmp, 5), ==, 5);
tt_int_op(memcmp(tmp, " have", 5), ==, 0);
/* Make sure that prepending does not meddle with immutable data */
tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0);
tt_int_op(memcmp(chunk1, "If you", 6), ==, 0);
evbuffer_validate(buf1);
/* Make sure we can drain a little from the second buffer. */
tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6);
tt_int_op(memcmp(tmp, "If you", 6), ==, 0);
tt_int_op(evbuffer_remove(buf2, tmp, 5), ==, 5);
tt_int_op(memcmp(tmp, " have", 5), ==, 0);
/* Make sure that prepending does not meddle with immutable data */
tt_int_op(evbuffer_prepend(buf2, "I have ", 7), ==, 0);
tt_int_op(memcmp(chunk1, "If you", 6), ==, 0);
evbuffer_validate(buf2);
/* Make sure the data can be read from the second buffer when the first is freed */
evbuffer_free(buf1);
buf1 = NULL;
tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6);
tt_int_op(memcmp(tmp, "I have", 6), ==, 0);
tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6);
tt_int_op(memcmp(tmp, " foun", 6), ==, 0);
end:
if (buf1)
evbuffer_free(buf1);
if (buf2)
evbuffer_free(buf2);
}
static void
test_evbuffer_multicast_drain(void *ptr)
{
const char chunk1[] = "If you have found the answer to such a problem";
const char chunk2[] = "you ought to write it up for publication";
/* -- Knuth's "Notes on the Exercises" from TAOCP */
size_t len1 = strlen(chunk1), len2=strlen(chunk2);
struct evbuffer *buf1 = NULL, *buf2 = NULL;
buf1 = evbuffer_new();
tt_assert(buf1);
evbuffer_add(buf1, chunk1, len1);
evbuffer_add(buf1, ", ", 2);
evbuffer_add(buf1, chunk2, len2);
tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2);
buf2 = evbuffer_new();
tt_assert(buf2);
tt_int_op(evbuffer_add_buffer_reference(buf2, buf1), ==, 0);
tt_int_op(evbuffer_get_length(buf2), ==, len1+len2+2);
tt_int_op(evbuffer_drain(buf1, evbuffer_get_length(buf1)), ==, 0);
tt_int_op(evbuffer_get_length(buf2), ==, len1+len2+2);
tt_int_op(evbuffer_drain(buf2, evbuffer_get_length(buf2)), ==, 0);
evbuffer_validate(buf1);
evbuffer_validate(buf2);
end:
if (buf1)
evbuffer_free(buf1);
if (buf2)
evbuffer_free(buf2);
}
/* Some cases that we didn't get in test_evbuffer() above, for more coverage. */
static void
test_evbuffer_prepend(void *ptr)
@ -2012,6 +2117,8 @@ struct testcase_t evbuffer_testcases[] = {
{ "search", test_evbuffer_search, 0, NULL, NULL },
{ "callbacks", test_evbuffer_callbacks, 0, NULL, NULL },
{ "add_reference", test_evbuffer_add_reference, 0, NULL, NULL },
{ "multicast", test_evbuffer_multicast, 0, NULL, NULL },
{ "multicast_drain", test_evbuffer_multicast_drain, 0, NULL, NULL },
{ "prepend", test_evbuffer_prepend, TT_FORK, NULL, NULL },
{ "peek", test_evbuffer_peek, 0, NULL, NULL },
{ "freeze_start", test_evbuffer_freeze, 0, &nil_setup, (void*)"start" },