Detect and refuse reentrant event_base_loop() calls

Calling event_base_loop on a base from inside a callback invoked by
that same base, or from two threads at once, has long been a way to
get exceedingly hard-to-diagnose errors.  This patch adds code to
detect such reentrant invocatinos, and exit quickly with a warning
that should explain what went wrong.
This commit is contained in:
Nick Mathewson 2010-03-21 13:28:48 -04:00
parent fb366c1d88
commit b557b175c0
3 changed files with 49 additions and 0 deletions

View File

@ -190,6 +190,10 @@ struct event_base {
/** Set if we should terminate the loop immediately */ /** Set if we should terminate the loop immediately */
int event_break; int event_break;
/** Set if we're running the event_base_loop function, to prevent
* reentrant invocation. */
int running_loop;
/* Active event management. */ /* Active event management. */
/** An array of nactivequeues queues for active events (ones that /** An array of nactivequeues queues for active events (ones that
* have triggered, and whose callbacks need to be called). Low * have triggered, and whose callbacks need to be called). Low

10
event.c
View File

@ -1399,6 +1399,15 @@ event_base_loop(struct event_base *base, int flags)
* as we invoke user callbacks. */ * as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock); EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) {
event_warn("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return -1;
}
base->running_loop = 1;
clear_time_cache(base); clear_time_cache(base);
if (base->sig.ev_signal_added) if (base->sig.ev_signal_added)
@ -1470,6 +1479,7 @@ event_base_loop(struct event_base *base, int flags)
done: done:
clear_time_cache(base); clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock); EVBASE_RELEASE_LOCK(base, th_base_lock);

View File

@ -1156,6 +1156,40 @@ end:
; ;
} }
static int reentrant_cb_run = 0;
static void
bad_reentrant_run_loop_cb(evutil_socket_t fd, short what, void *ptr)
{
struct event_base *base = ptr;
int r;
reentrant_cb_run = 1;
/* This reentrant call to event_base_loop should be detected and
* should fail */
r = event_base_loop(base, 0);
tt_int_op(r, ==, -1);
end:
;
}
static void
test_bad_reentrant(void *ptr)
{
struct basic_test_data *data = ptr;
struct event_base *base = data->base;
struct event ev;
int r;
event_assign(&ev, base, -1,
0, bad_reentrant_run_loop_cb, base);
event_active(&ev, EV_WRITE, 1);
r = event_base_loop(base, 0);
tt_int_op(r, ==, 1);
tt_int_op(reentrant_cb_run, ==, 1);
end:
;
}
static void static void
test_event_base_new(void *ptr) test_event_base_new(void *ptr)
{ {
@ -2072,6 +2106,7 @@ struct testcase_t main_testcases[] = {
BASIC(manipulate_active_events, TT_FORK|TT_NEED_BASE), BASIC(manipulate_active_events, TT_FORK|TT_NEED_BASE),
BASIC(bad_assign, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), BASIC(bad_assign, TT_FORK|TT_NEED_BASE|TT_NO_LOGS),
BASIC(bad_reentrant, TT_FORK|TT_NEED_BASE|TT_NO_LOGS),
/* These are still using the old API */ /* These are still using the old API */
LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE), LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE),