mirror of
https://github.com/libevent/libevent.git
synced 2025-01-09 00:56:20 +08:00
Merge remote-tracking branch 'fancycode/buffer_references'
Conflicts: buffer.c
This commit is contained in:
commit
de5428e7d5
120
buffer.c
120
buffer.c
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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" },
|
||||
|
Loading…
x
Reference in New Issue
Block a user