Improve robustness for refcounting

Document that we do intend to double-decref underlying bufferevents under
some circumstances.  Check to make sure that we don't decref past 0.
This commit is contained in:
Nick Mathewson 2010-03-12 23:00:49 -05:00
parent 6c83e6c972
commit f1bc125eb4
5 changed files with 61 additions and 15 deletions

View File

@ -435,6 +435,8 @@ _evbuffer_decref_and_unlock(struct evbuffer *buffer)
struct evbuffer_chain *chain, *next; struct evbuffer_chain *chain, *next;
ASSERT_EVBUFFER_LOCKED(buffer); ASSERT_EVBUFFER_LOCKED(buffer);
EVUTIL_ASSERT(buffer->refcnt > 0);
if (--buffer->refcnt > 0) { if (--buffer->refcnt > 0) {
EVBUFFER_UNLOCK(buffer); EVBUFFER_UNLOCK(buffer);
return; return;

View File

@ -280,11 +280,12 @@ void bufferevent_incref(struct bufferevent *bufev);
/** Internal: Lock bufev and increase its reference count. /** Internal: Lock bufev and increase its reference count.
* unlocking it otherwise. */ * unlocking it otherwise. */
void _bufferevent_incref_and_lock(struct bufferevent *bufev); void _bufferevent_incref_and_lock(struct bufferevent *bufev);
/** Internal: Increment the reference count on bufev. */ /** Internal: Decrement the reference count on bufev. Returns 1 if it freed
void bufferevent_decref(struct bufferevent *bufev); * the bufferevent.*/
int bufferevent_decref(struct bufferevent *bufev);
/** Internal: Drop the reference count on bufev, freeing as necessary, and /** Internal: Drop the reference count on bufev, freeing as necessary, and
* unlocking it otherwise. */ * unlocking it otherwise. Returns 1 if it freed the bufferevent. */
void _bufferevent_decref_and_unlock(struct bufferevent *bufev); int _bufferevent_decref_and_unlock(struct bufferevent *bufev);
/** Internal: If callbacks are deferred and we have a read callback, schedule /** Internal: If callbacks are deferred and we have a read callback, schedule
* a readcb. Otherwise just run the readcb. */ * a readcb. Otherwise just run the readcb. */

View File

@ -504,16 +504,36 @@ _bufferevent_incref_and_lock(struct bufferevent *bufev)
++bufev_private->refcnt; ++bufev_private->refcnt;
} }
void #if 0
static void
_bufferevent_transfer_lock_ownership(struct bufferevent *donor,
struct bufferevent *recipient)
{
struct bufferevent_private *d = BEV_UPCAST(donor);
struct bufferevent_private *r = BEV_UPCAST(recipient);
if (d->lock != r->lock)
return;
if (r->own_lock)
return;
if (d->own_lock) {
d->own_lock = 0;
r->own_lock = 1;
}
}
#endif
int
_bufferevent_decref_and_unlock(struct bufferevent *bufev) _bufferevent_decref_and_unlock(struct bufferevent *bufev)
{ {
struct bufferevent_private *bufev_private = struct bufferevent_private *bufev_private =
EVUTIL_UPCAST(bufev, struct bufferevent_private, bev); EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
struct bufferevent *underlying; struct bufferevent *underlying;
EVUTIL_ASSERT(bufev_private->refcnt > 0);
if (--bufev_private->refcnt) { if (--bufev_private->refcnt) {
BEV_UNLOCK(bufev); BEV_UNLOCK(bufev);
return; return 0;
} }
underlying = bufferevent_get_underlying(bufev); underlying = bufferevent_get_underlying(bufev);
@ -550,21 +570,27 @@ _bufferevent_decref_and_unlock(struct bufferevent *bufev)
/* Free the actual allocated memory. */ /* Free the actual allocated memory. */
mm_free(bufev - bufev->be_ops->mem_offset); mm_free(bufev - bufev->be_ops->mem_offset);
/* release the reference to underlying now that we no longer need /* Release the reference to underlying now that we no longer need the
* the reference to it. This is mainly in case our lock is shared * reference to it. We wait this long mainly in case our lock is
* with underlying. * shared with underlying.
*
* The 'destruct' function will also drop a reference to underlying
* if BEV_OPT_CLOSE_ON_FREE is set.
*
* XXX Should we/can we just refcount evbuffer/bufferevent locks? * XXX Should we/can we just refcount evbuffer/bufferevent locks?
* It would probably save us some headaches. * It would probably save us some headaches.
*/ */
if (underlying) if (underlying)
bufferevent_decref(underlying); bufferevent_decref(underlying);
return 1;
} }
void int
bufferevent_decref(struct bufferevent *bufev) bufferevent_decref(struct bufferevent *bufev)
{ {
BEV_LOCK(bufev); BEV_LOCK(bufev);
_bufferevent_decref_and_unlock(bufev); return _bufferevent_decref_and_unlock(bufev);
} }
void void

View File

@ -187,6 +187,7 @@ bufferevent_filter_new(struct bufferevent *underlying,
} }
bufev_f->underlying = underlying; bufev_f->underlying = underlying;
bufev_f->process_in = input_filter; bufev_f->process_in = input_filter;
bufev_f->process_out = output_filter; bufev_f->process_out = output_filter;
bufev_f->free_context = free_context; bufev_f->free_context = free_context;
@ -212,8 +213,19 @@ be_filter_destruct(struct bufferevent *bev)
if (bevf->free_context) if (bevf->free_context)
bevf->free_context(bevf->context); bevf->free_context(bevf->context);
if (bevf->bev.options & BEV_OPT_CLOSE_ON_FREE) if (bevf->bev.options & BEV_OPT_CLOSE_ON_FREE) {
bufferevent_free(bevf->underlying); /* Yes, there is also a decref in bufferevent_decref.
* That decref corresponds to the incref when we set
* underlying for the first time. This decref is an
* extra one to remove the last reference.
*/
if (BEV_UPCAST(bevf->underlying)->refcnt < 2) {
event_warnx("BEV_OPT_CLOSE_ON_FREE set on an "
"bufferevent with too few references");
} else {
bufferevent_free(bevf->underlying);
}
}
_bufferevent_del_generic_timeout_cbs(bev); _bufferevent_del_generic_timeout_cbs(bev);
} }

View File

@ -1030,8 +1030,13 @@ be_openssl_destruct(struct bufferevent *bev)
if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) {
if (bev_ssl->underlying) { if (bev_ssl->underlying) {
bufferevent_free(bev_ssl->underlying); if (BEV_UPCAST(bev_ssl->underlying)->refcnt < 2) {
bev_ssl->underlying = NULL; event_warnx("BEV_OPT_CLOSE_ON_FREE set on an "
"bufferevent with too few references");
} else {
bufferevent_free(bev_ssl->underlying);
bev_ssl->underlying = NULL;
}
} }
SSL_free(bev_ssl->ssl); SSL_free(bev_ssl->ssl);
} }