Hi,

On 10.06.2011 23:21, Nick Mathewson wrote:
[...]
I think the locking mechanism has a few potential deadlocks and race
conditions we need to worry about.
[...]

I finally got some time to continue working on this. Attached are more
changes against my first patch, that should resolve the deadlocks/race
conditions.
It basically prevents nesting of already referenced buffers, so you
can't add a buffer that contains references to other buffers. Also the
referenced source buffer is stored, so the lock doesn't get freed too
early.

Best regards,
  Joachim
>From e613b30a52ef54068e1d95504a0887e8870654d0 Mon Sep 17 00:00:00 2001
From: Joachim Bauch <m...@joachim-bauch.de>
Date: Thu, 9 Jun 2011 23:33:58 +0200
Subject: [PATCH 1/2] support adding buffers to other buffers non-destructively

---
 buffer.c                |  131 ++++++++++++++++++++++++++++++++++++++++++++++-
 evbuffer-internal.h     |   15 +++++
 include/event2/buffer.h |   12 ++++
 test/regress_buffer.c   |   68 ++++++++++++++++++++++++
 4 files changed, 225 insertions(+), 1 deletions(-)

diff --git a/buffer.c b/buffer.c
index 84d3aea..c27cf00 100644
--- a/buffer.c
+++ b/buffer.c
@@ -142,6 +142,8 @@ static struct evbuffer_chain *evbuffer_expand_singlechain(struct evbuffer *buf,
     size_t datlen);
 static int evbuffer_ptr_subtract(struct evbuffer *buf, struct evbuffer_ptr *pos,
     size_t howfar);
+static inline void evbuffer_chain_incref(struct evbuffer_chain *chain);
+static inline void evbuffer_chain_decref(struct evbuffer_chain *chain);
 
 static struct evbuffer_chain *
 evbuffer_chain_new(size_t size)
@@ -179,7 +181,11 @@ evbuffer_chain_free(struct evbuffer_chain *chain)
 		chain->flags |= EVBUFFER_DANGLING;
 		return;
 	}
-
+	if (chain->refcnt > 0) {
+		// chain is still referenced by other chains
+		return;
+	}
+    
 	if (chain->flags & EVBUFFER_REFERENCE) {
 		struct evbuffer_chain_reference *info =
 		    EVBUFFER_CHAIN_EXTRA(
@@ -203,6 +209,16 @@ 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);
+		EVUTIL_ASSERT(info->parent != NULL);
+		EVBUFFER_LOCK(info);
+		evbuffer_chain_decref(info->parent);
+		EVBUFFER_UNLOCK(info);
+	}
 
 	mm_free(chain);
 }
@@ -292,6 +308,23 @@ _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;
+}
+
+static inline void
+evbuffer_chain_decref(struct evbuffer_chain *chain)
+{
+    EVUTIL_ASSERT(chain->refcnt > 0);
+    if (--chain->refcnt > 0) {
+        return;
+    }
+    
+    evbuffer_chain_free(chain);
+}
+
 struct evbuffer *
 evbuffer_new(void)
 {
@@ -770,6 +803,59 @@ 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);
+		if (chain->flags & EVBUFFER_MULTICAST) {
+			// source chain is a reference itself
+			struct evbuffer_multicast_parent *info =
+				EVBUFFER_CHAIN_EXTRA(
+					struct evbuffer_multicast_parent,
+					chain);
+#ifndef _EVENT_DISABLE_THREAD_SUPPORT
+			extra->lock = info->lock;
+#endif
+			EVBUFFER_LOCK(info);
+			evbuffer_chain_incref(info->parent);
+			EVBUFFER_UNLOCK(info);
+			extra->parent = info->parent;
+		} else {
+			// reference source chain which now becomes immutable
+			evbuffer_chain_incref(chain);
+#ifndef _EVENT_DISABLE_THREAD_SUPPORT
+			extra->lock = src->lock;
+#endif
+			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)
 {
@@ -835,6 +921,49 @@ done:
 }
 
 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)) != 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)
 {
 	struct evbuffer_chain *pinned, *last;
diff --git a/evbuffer-internal.h b/evbuffer-internal.h
index 6bb3e2c..75d026d 100644
--- a/evbuffer-internal.h
+++ b/evbuffer-internal.h
@@ -183,6 +183,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 multicast 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.
@@ -240,6 +245,16 @@ 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 {
+#ifndef _EVENT_DISABLE_THREAD_SUPPORT
+	void *lock; /**< lock prevent concurrent access to the parent */
+#endif
+	/** 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)
diff --git a/include/event2/buffer.h b/include/event2/buffer.h
index 7e2fc31..e0fb685 100644
--- a/include/event2/buffer.h
+++ b/include/event2/buffer.h
@@ -333,6 +333,18 @@ 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.
+  
+  @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);
+
 
 typedef void (*evbuffer_ref_cleanup_cb)(const void *data,
     size_t datalen, void *extra);
diff --git a/test/regress_buffer.c b/test/regress_buffer.c
index 9c4b47c..74bc1ec 100644
--- a/test/regress_buffer.c
+++ b/test/regress_buffer.c
@@ -1478,6 +1478,73 @@ 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);
+    tt_int_op(evbuffer_add_buffer_reference(buf2, 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);
+}
+
 /* Some cases that we didn't get in test_evbuffer() above, for more coverage. */
 static void
 test_evbuffer_prepend(void *ptr)
@@ -1761,6 +1828,7 @@ 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 },
 	{ "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" },
-- 
1.7.4.1

>From 9f169a7ce5c8744e81dc8e190fdaa24db0a37364 Mon Sep 17 00:00:00 2001
From: Joachim Bauch <m...@joachim-bauch.de>
Date: Thu, 4 Aug 2011 23:39:15 +0200
Subject: [PATCH 2/2] prevent nested multicast references, reworked locking

---
 buffer.c                |   42 ++++++++++++------------------------------
 evbuffer-internal.h     |    5 ++---
 include/event2/buffer.h |    6 +++++-
 test/regress_buffer.c   |    2 ++
 4 files changed, 21 insertions(+), 34 deletions(-)

diff --git a/buffer.c b/buffer.c
index 7c9d62c..e2c7f2f 100644
--- a/buffer.c
+++ b/buffer.c
@@ -221,10 +221,11 @@ evbuffer_chain_free(struct evbuffer_chain *chain)
 		    EVBUFFER_CHAIN_EXTRA(
 			    struct evbuffer_multicast_parent,
 			    chain);
+		EVUTIL_ASSERT(info->source != NULL);
 		EVUTIL_ASSERT(info->parent != NULL);
-		EVBUFFER_LOCK(info);
+		EVBUFFER_LOCK(info->source);
 		evbuffer_chain_decref(info->parent);
-		EVBUFFER_UNLOCK(info);
+		_evbuffer_decref_and_unlock(info->source);
 	}
 
 	mm_free(chain);
@@ -325,11 +326,8 @@ static inline void
 evbuffer_chain_decref(struct evbuffer_chain *chain)
 {
     EVUTIL_ASSERT(chain->refcnt > 0);
-    if (--chain->refcnt > 0) {
-        return;
-    }
-    
-    evbuffer_chain_free(chain);
+    --chain->refcnt;
+    // chain will be freed when parent buffer is released
 }
 
 struct evbuffer *
@@ -837,28 +835,12 @@ APPEND_CHAIN_MULTICAST(struct evbuffer *dst, struct evbuffer *src)
 			return;
 		}
 		extra = EVBUFFER_CHAIN_EXTRA(struct evbuffer_multicast_parent, tmp);
-		if (chain->flags & EVBUFFER_MULTICAST) {
-			// source chain is a reference itself
-			struct evbuffer_multicast_parent *info =
-				EVBUFFER_CHAIN_EXTRA(
-					struct evbuffer_multicast_parent,
-					chain);
-#ifndef _EVENT_DISABLE_THREAD_SUPPORT
-			extra->lock = info->lock;
-#endif
-			EVBUFFER_LOCK(info);
-			evbuffer_chain_incref(info->parent);
-			EVBUFFER_UNLOCK(info);
-			extra->parent = info->parent;
-		} else {
-			// reference source chain which now becomes immutable
-			evbuffer_chain_incref(chain);
-#ifndef _EVENT_DISABLE_THREAD_SUPPORT
-			extra->lock = src->lock;
-#endif
-			extra->parent = chain;
-			chain->flags |= EVBUFFER_IMMUTABLE;
-		}
+		// reference source chain which now becomes immutable
+		_evbuffer_incref(src);
+		extra->source = src;
+		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;
@@ -953,7 +935,7 @@ evbuffer_add_buffer_reference(struct evbuffer *outbuf, struct evbuffer *inbuf)
 	}
 
 	for (; chain; chain = chain->next) {
-		if ((chain->flags & (EVBUFFER_FILESEGMENT|EVBUFFER_SENDFILE)) != 0) {
+		if ((chain->flags & (EVBUFFER_FILESEGMENT|EVBUFFER_SENDFILE|EVBUFFER_MULTICAST)) != 0) {
 			// chain type can not be referenced
 			result = -1;
 			goto done;
diff --git a/evbuffer-internal.h b/evbuffer-internal.h
index 75d026d..1d67973 100644
--- a/evbuffer-internal.h
+++ b/evbuffer-internal.h
@@ -248,9 +248,8 @@ struct evbuffer_file_segment {
 /** 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 {
-#ifndef _EVENT_DISABLE_THREAD_SUPPORT
-	void *lock; /**< lock prevent concurrent access to the parent */
-#endif
+	/** source buffer the multicast parent belongs to */
+	struct evbuffer *source;
 	/** multicast parent for this chain */
 	struct evbuffer_chain *parent;
 };
diff --git a/include/event2/buffer.h b/include/event2/buffer.h
index b8135f0..951137f 100644
--- a/include/event2/buffer.h
+++ b/include/event2/buffer.h
@@ -393,11 +393,15 @@ int evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf);
   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);
+int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
+    struct evbuffer *inbuf);
 
 /**
    A cleanup function for a piece of memory added to an evbuffer by
diff --git a/test/regress_buffer.c b/test/regress_buffer.c
index 0eda87c..00240d9 100644
--- a/test/regress_buffer.c
+++ b/test/regress_buffer.c
@@ -1544,7 +1544,9 @@ test_evbuffer_multicast(void *ptr)
 	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));
-- 
1.7.4.1

Reply via email to