From c735f2b45aaca4d9c118245e407a00e6b8f4d234 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 23 Jan 2009 01:11:13 +0000 Subject: [PATCH] Code to allow multiple callbacks per evbuffer. svn:r1042 --- buffer.c | 128 ++++++++++++++++++++++++----------- evbuffer-internal.h | 12 +++- include/event2/buffer.h | 57 +++++++++++++--- include/event2/bufferevent.h | 3 + whatsnew-xx.txt | 2 + 5 files changed, 151 insertions(+), 51 deletions(-) diff --git a/buffer.c b/buffer.c index cca2e7f2..78e91c63 100644 --- a/buffer.c +++ b/buffer.c @@ -103,12 +103,42 @@ struct evbuffer * evbuffer_new(void) { struct evbuffer *buffer; - + buffer = mm_calloc(1, sizeof(struct evbuffer)); + TAILQ_INIT(&buffer->callbacks); + return (buffer); } +static inline void +evbuffer_invoke_callbacks(struct evbuffer *buffer, size_t old_size) +{ + size_t new_size = buffer->total_len; + if (!TAILQ_EMPTY(&buffer->callbacks) && old_size != new_size + && !buffer->in_callbacks) { + struct evbuffer_cb_entry *cbent, *next; + buffer->in_callbacks = 1; + for (cbent = TAILQ_FIRST(&buffer->callbacks); + cbent != TAILQ_END(&buffer->callbacks); + cbent = next) { + next = TAILQ_NEXT(cbent, next); + cbent->cb(buffer, old_size, new_size, cbent->cbarg); + } + buffer->in_callbacks = 0; + } +} + +static void +evbuffer_remove_all_callbacks(struct evbuffer *buffer) +{ + struct evbuffer_cb_entry *cbent, *next; + while ((cbent = TAILQ_FIRST(&buffer->callbacks))) { + TAILQ_REMOVE(&buffer->callbacks, cbent, next); + mm_free(cbent); + } +} + void evbuffer_free(struct evbuffer *buffer) { @@ -117,6 +147,7 @@ evbuffer_free(struct evbuffer *buffer) next = chain->next; mm_free(chain); } + evbuffer_remove_all_callbacks(buffer); mm_free(buffer); } @@ -211,13 +242,9 @@ evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) /* remove everything from inbuf */ ZERO_CHAIN(inbuf); - if (inbuf->cb != NULL && inbuf->total_len != in_total_len) - (*inbuf->cb)(inbuf, in_total_len, inbuf->total_len, - inbuf->cbarg); - if (outbuf->cb != NULL && outbuf->total_len != out_total_len) - (*outbuf->cb)(outbuf, out_total_len, outbuf->total_len, - outbuf->cbarg); - + evbuffer_invoke_callbacks(inbuf, in_total_len); + evbuffer_invoke_callbacks(outbuf, out_total_len); + return (0); } @@ -239,12 +266,8 @@ evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) /* remove everything from inbuf */ ZERO_CHAIN(inbuf); - if (inbuf->cb != NULL && inbuf->total_len != in_total_len) - (*inbuf->cb)(inbuf, in_total_len, inbuf->total_len, - inbuf->cbarg); - if (outbuf->cb != NULL && outbuf->total_len != out_total_len) - (*outbuf->cb)(outbuf, out_total_len, outbuf->total_len, - outbuf->cbarg); + evbuffer_invoke_callbacks(inbuf, in_total_len); + evbuffer_invoke_callbacks(outbuf, out_total_len); } void @@ -282,9 +305,7 @@ evbuffer_drain(struct evbuffer *buf, size_t len) } /* Tell someone about changes in this buffer */ - if (buf->cb != NULL && buf->total_len != old_len) - (*buf->cb)(buf, old_len, buf->total_len, buf->cbarg); - + evbuffer_invoke_callbacks(buf, old_len); } /* Reads data from an event buffer and drains the bytes read */ @@ -328,9 +349,8 @@ evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen) buf->total_len -= nread; - if (buf->cb != NULL && nread) - (*buf->cb)(buf, buf->total_len + nread, buf->total_len, - buf->cbarg); + if (nread) + evbuffer_invoke_callbacks(buf, buf->total_len + nread); return (nread); } @@ -390,12 +410,10 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst, src->total_len -= nread; - if (dst->cb != NULL && nread) - (*dst->cb)(dst, dst->total_len - nread, dst->total_len, - dst->cbarg); - if (src->cb != NULL && nread) - (*src->cb)(src, src->total_len + nread, src->total_len, - src->cbarg); + if (nread) { + evbuffer_invoke_callbacks(dst, dst->total_len - nread); + evbuffer_invoke_callbacks(src, src->total_len + nread); + } return (nread); } @@ -709,8 +727,7 @@ evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen) chain->off = datlen; out: - if (buf->cb != NULL && datlen) - (*buf->cb)(buf, old_len, buf->total_len, buf->cbarg); + evbuffer_invoke_callbacks(buf, old_len); return (0); } @@ -754,8 +771,7 @@ evbuffer_prepend(struct evbuffer *buf, const void *data, size_t datlen) } buf->total_len += datlen; - if (buf->cb != NULL && datlen) - (*buf->cb)(buf, old_len, buf->total_len, buf->cbarg); + evbuffer_invoke_callbacks(buf, old_len); return (0); } @@ -1052,8 +1068,7 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) buf->total_len += n; /* Tell someone about changes in this buffer */ - if (buf->cb != NULL) - (*buf->cb)(buf, old_len, buf->total_len, buf->cbarg); + evbuffer_invoke_callbacks(buf, old_len); return (n); } @@ -1176,9 +1191,8 @@ evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap) if (sz < space) { chain->off += sz; buf->total_len += sz; - if (buf->cb != NULL) - (*buf->cb)(buf, old_len, - buf->total_len, buf->cbarg); + + evbuffer_invoke_callbacks(buf, old_len); return (sz); } if (evbuffer_expand(buf, sz + 1) == -1) @@ -1202,10 +1216,44 @@ evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...) } void -evbuffer_setcb(struct evbuffer *buffer, - void (*cb)(struct evbuffer *, size_t, size_t, void *), - void *cbarg) +evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg) { - buffer->cb = cb; - buffer->cbarg = cbarg; + if (!TAILQ_EMPTY(&buffer->callbacks)) + evbuffer_remove_all_callbacks(buffer); + + if (cb) + evbuffer_add_cb(buffer, cb, cbarg); +} + +struct evbuffer_cb_entry * +evbuffer_add_cb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg) +{ + struct evbuffer_cb_entry *e; + if (! (e = mm_malloc(sizeof(struct evbuffer_cb_entry)))) + return NULL; + e->cb = cb; + e->cbarg = cbarg; + TAILQ_INSERT_HEAD(&buffer->callbacks, e, next); + return e; +} + +int +evbuffer_remove_cb_entry(struct evbuffer *buffer, + struct evbuffer_cb_entry *ent) +{ + TAILQ_REMOVE(&buffer->callbacks, ent, next); + mm_free(ent); + return 0; +} + +int +evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg) +{ + struct evbuffer_cb_entry *cbent; + TAILQ_FOREACH(cbent, &buffer->callbacks, next) { + if (cb == cbent->cb && cbarg == cbent->cbarg) { + return evbuffer_remove_cb_entry(buffer, cbent); + } + } + return -1; } diff --git a/evbuffer-internal.h b/evbuffer-internal.h index 4637d87f..5f7fc109 100644 --- a/evbuffer-internal.h +++ b/evbuffer-internal.h @@ -34,9 +34,16 @@ extern "C" { #include "config.h" #include "evutil.h" +#include /* minimum allocation */ #define MIN_BUFFER_SIZE 256 +struct evbuffer_cb_entry { + TAILQ_ENTRY(evbuffer_cb_entry) next; + evbuffer_cb cb; + void *cbarg; +}; + struct evbuffer_chain; struct evbuffer { struct evbuffer_chain *first; @@ -45,8 +52,11 @@ struct evbuffer { size_t total_len; /* total length of all buffers */ - void (*cb)(struct evbuffer *, size_t, size_t, void *); + evbuffer_cb cb; void *cbarg; + + TAILQ_HEAD(evbuffer_cb_queue, evbuffer_cb_entry) callbacks; + unsigned char in_callbacks; }; struct evbuffer_chain { diff --git a/include/event2/buffer.h b/include/event2/buffer.h index 1194c056..66e54dd1 100644 --- a/include/event2/buffer.h +++ b/include/event2/buffer.h @@ -326,22 +326,59 @@ int evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch); */ unsigned char *evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len); -/** - Set a callback to invoke when the evbuffer is modified. - The callback takes four arguments. In order, they are: the evbuffer that - was modified, the buffer's old length, the buffer's new length, and the - value passed to this function in the 'cbarg' argument. +/** Type definition for a callback that is invoked whenever[1] data is added or + removed from an evbuffer. - Subsequent calls to evbuffer_setcb() replace callbacks set by previous - calls. Setting the callback to NULL removes any previously set callback. + An evbuffer may have one or more callbacks set at a time. The order + in which they are exectuded is undefined. + + A callback function may add more callbacks, or remove itself from the + list of callbacks, or add or remove data from the buffer. It may not + remove another callback from the list. + + [1] If the length of the buffer changes because of a callback, the + callbacks are not invoked again, to prevent an infinite loop. + + @param buffer the buffer whose size has changed + @param old_len the previous length of the buffer + @param new_len thee current length of the buffer + @param arg a pointer to user data +*/ +typedef void (*evbuffer_cb)(struct evbuffer *buffer, size_t old_len, size_t new_len, void *arg); + +struct evbuffer_cb_entry; +/** Add a new callback to an evbuffer. + + Subsequent calls to evbuffer_add_cb() add new callbacks. To remove this + callback, call evbuffer_remove_cb or evbuffer_remove_cb_entry. @param buffer the evbuffer to be monitored - @param cb the callback function to invoke when the evbuffer is modified + @param cb the callback function to invoke when the evbuffer is modified, + or NULL to remove all callbacks. @param cbarg an argument to be provided to the callback function + @return a handle to the callback on success, or NULL on failure. */ -void evbuffer_setcb(struct evbuffer *buffer, - void (*cb)(struct evbuffer *, size_t, size_t, void *), void *cbarg); +struct evbuffer_cb_entry *evbuffer_add_cb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg); + +/** Remove a callback from an evbuffer, given a handle returned from + evbuffer_add_cb. + + Calling this function invalidates the handle. + + @return 0 if a callback was removed, or -1 if no matching callback was + found. + */ +int evbuffer_remove_cb_entry(struct evbuffer *buffer, + struct evbuffer_cb_entry *ent); + +/** Remove a callback from an evbuffer, given the function and argument + used to add it. + + @return 0 if a callback was removed, or -1 if no matching callback was + found. + */ +int evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg); /** Makes the data at the begging of an evbuffer contiguous. diff --git a/include/event2/bufferevent.h b/include/event2/bufferevent.h index ffe14738..d0b54191 100644 --- a/include/event2/bufferevent.h +++ b/include/event2/bufferevent.h @@ -81,6 +81,8 @@ struct evbuffer; @param bev the bufferevent that triggered the callback @param ctx the user specified context for this bufferevent */ +/* XXXX we should rename this to bufferevent_cb; evbuffercb implies that it's + * a cb on an evbuffer. We should retain the old name in bufferevent_compat. */ typedef void (*evbuffercb)(struct bufferevent *bev, void *ctx); /** @@ -96,6 +98,7 @@ typedef void (*evbuffercb)(struct bufferevent *bev, void *ctx); EVBUFFER_TIMEOUT. @param ctx the user specified context for this bufferevent */ +/* XXXX we should rename this to bufferevent_error_cb; see above. */ typedef void (*everrorcb)(struct bufferevent *bev, short what, void *ctx); diff --git a/whatsnew-xx.txt b/whatsnew-xx.txt index c7290426..6ec5bf7d 100644 --- a/whatsnew-xx.txt +++ b/whatsnew-xx.txt @@ -175,6 +175,8 @@ What's New In Libevent 2.0 so far: 2.X. Edge-triggered events on some backends. +2.X. Multiple callbacks per evbuffer + 3. Big bugfixes 3.X. Win32 bufferevents work