diff options
author | Nick Mathewson <nickm@torproject.org> | 2013-03-28 14:13:19 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2013-04-26 12:18:07 -0400 |
commit | 8eedeabe50563ac6d679700ca9856a67875ceb39 (patch) | |
tree | 9a34aec1f7d11a79dda2386f62f3d690daa36ab9 | |
parent | 2fad0f3d52c6c7511231b1b2eced484306835a52 (diff) | |
download | libevent-8eedeabe50563ac6d679700ca9856a67875ceb39.tar.gz |
Implement event_finalize() and related functions to avoid certain deadlocks
-rw-r--r-- | event-internal.h | 17 | ||||
-rw-r--r-- | event.c | 210 | ||||
-rw-r--r-- | include/event2/event.h | 56 | ||||
-rw-r--r-- | include/event2/event_struct.h | 12 | ||||
-rw-r--r-- | test/include.am | 2 | ||||
-rw-r--r-- | test/regress.h | 1 | ||||
-rw-r--r-- | test/regress_finalize.c | 342 | ||||
-rw-r--r-- | test/regress_main.c | 1 | ||||
-rw-r--r-- | test/regress_thread.c | 19 | ||||
-rw-r--r-- | test/regress_thread.h | 48 |
10 files changed, 662 insertions, 46 deletions
diff --git a/event-internal.h b/event-internal.h index 614f5d80..07d29e32 100644 --- a/event-internal.h +++ b/event-internal.h @@ -60,10 +60,14 @@ extern "C" { #define ev_arg ev_evcallback.evcb_arg /* Possible values for evcb_closure in struct event_callback */ +/* DOCUMENT these. */ #define EV_CLOSURE_EVENT 0 #define EV_CLOSURE_EVENT_SIGNAL 1 #define EV_CLOSURE_EVENT_PERSIST 2 #define EV_CLOSURE_CB_SELF 3 +#define EV_CLOSURE_CB_FINALIZE 4 +#define EV_CLOSURE_EVENT_FINALIZE 5 +#define EV_CLOSURE_EVENT_FINALIZE_FREE 6 /** Structure to define the backend of a given event_base. */ struct eventop { @@ -382,7 +386,11 @@ int evsig_restore_handler_(struct event_base *base, int evsignal); int event_add_nolock_(struct event *ev, const struct timeval *tv, int tv_is_absolute); -int event_del_nolock_(struct event *ev); +#define EVENT_DEL_NOBLOCK 0 +#define EVENT_DEL_BLOCK 1 +#define EVENT_DEL_AUTOBLOCK 2 +#define EVENT_DEL_EVEN_IF_FINALIZING 3 +int event_del_nolock_(struct event *ev, int blocking); int event_remove_timer_nolock_(struct event *ev); void event_active_nolock_(struct event *ev, int res, short count); @@ -391,12 +399,17 @@ int event_callback_activate_nolock_(struct event_base *, struct event_callback * int event_callback_cancel_(struct event_base *base, struct event_callback *evcb); +void event_callback_finalize_nolock_(struct event_base *base, unsigned flags, struct event_callback *evcb, void (*cb)(struct event_callback *, void *)); +void event_callback_finalize_(struct event_base *base, unsigned flags, struct event_callback *evcb, void (*cb)(struct event_callback *, void *)); +int event_callback_finalize_many_(struct event_base *base, int n_cbs, struct event_callback **evcb, void (*cb)(struct event_callback *, void *)); + + void event_active_later_(struct event *ev, int res); void event_active_later_nolock_(struct event *ev, int res); void event_callback_activate_later_nolock_(struct event_base *base, struct event_callback *evcb); int event_callback_cancel_nolock_(struct event_base *base, - struct event_callback *evcb); + struct event_callback *evcb, int even_if_finalizing); void event_callback_init_(struct event_base *base, struct event_callback *cb); @@ -142,7 +142,7 @@ static void event_queue_remove_inserted(struct event_base *, struct event *); static void event_queue_make_later_events_active(struct event_base *base); static int evthread_make_base_notifiable_nolock_(struct event_base *base); - +static int event_del_(struct event *ev, int blocking); #ifdef USE_REINSERT_TIMEOUT /* This code seems buggy; only turn it on if we find out what the trouble is. */ @@ -776,11 +776,11 @@ event_base_free(struct event_base *base) if (evcb->evcb_flags & EVLIST_INIT) { ev = event_callback_to_event(evcb); if (!(ev->ev_flags & EVLIST_INTERNAL)) { - event_del(ev); + event_del_(ev, EVENT_DEL_EVEN_IF_FINALIZING); ++n_deleted; } } else { - event_callback_cancel_(base, evcb); + event_callback_cancel_nolock_(base, evcb, 1); ++n_deleted; } evcb = next; @@ -794,7 +794,7 @@ event_base_free(struct event_base *base) event_del(ev); ++n_deleted; } else { - event_callback_cancel_(base, evcb); + event_callback_cancel_nolock_(base, evcb, 1); ++n_deleted; } } @@ -884,7 +884,7 @@ event_reinit(struct event_base *base) * random. */ if (base->sig.ev_signal_added) { - event_del_nolock_(&base->sig.ev_signal); + event_del_nolock_(&base->sig.ev_signal, EVENT_DEL_AUTOBLOCK); event_debug_unassign(&base->sig.ev_signal); memset(&base->sig.ev_signal, 0, sizeof(base->sig.ev_signal)); if (base->sig.ev_signal_pair[0] != -1) @@ -899,7 +899,7 @@ event_reinit(struct event_base *base) base->th_notify_fn = NULL; } if (base->th_notify_fd[0] != -1) { - event_del_nolock_(&base->th_notify); + event_del_nolock_(&base->th_notify, EVENT_DEL_AUTOBLOCK); EVUTIL_CLOSESOCKET(base->th_notify_fd[0]); if (base->th_notify_fd[1] != -1) EVUTIL_CLOSESOCKET(base->th_notify_fd[1]); @@ -1278,7 +1278,7 @@ common_timeout_callback(evutil_socket_t fd, short what, void *arg) (ev->ev_timeout.tv_sec == now.tv_sec && (ev->ev_timeout.tv_usec&MICROSECONDS_MASK) > now.tv_usec)) break; - event_del_nolock_(ev); + event_del_nolock_(ev, EVENT_DEL_NOBLOCK); event_active_nolock_(ev, EV_TIMEOUT, 1); } if (ev) @@ -1433,10 +1433,10 @@ event_process_active_single_queue(struct event_base *base, if (evcb->evcb_flags & EVLIST_INIT) { ev = event_callback_to_event(evcb); - if (ev->ev_events & EV_PERSIST) + if (ev->ev_events & EV_PERSIST || ev->ev_flags & EVLIST_FINALIZING) event_queue_remove_active(base, evcb); else - event_del_nolock_(ev); + event_del_nolock_(ev, EVENT_DEL_NOBLOCK); event_debug(( "event_process_active: event: %p, %s%scall %p", ev, @@ -1475,6 +1475,23 @@ event_process_active_single_queue(struct event_base *base, EVBASE_RELEASE_LOCK(base, th_base_lock); evcb->evcb_cb_union.evcb_selfcb(evcb, evcb->evcb_arg); break; + case EV_CLOSURE_EVENT_FINALIZE: + case EV_CLOSURE_EVENT_FINALIZE_FREE: + base->current_event = NULL; + EVUTIL_ASSERT((evcb->evcb_flags & EVLIST_FINALIZING)); + EVBASE_RELEASE_LOCK(base, th_base_lock); + ev->ev_evcallback.evcb_cb_union.evcb_evfinalize(ev, ev->ev_arg); + if (evcb->evcb_closure == EV_CLOSURE_EVENT_FINALIZE_FREE) + mm_free(ev); + event_debug_note_teardown_(ev); + break; + case EV_CLOSURE_CB_FINALIZE: + base->current_event = NULL; + EVUTIL_ASSERT((evcb->evcb_flags & EVLIST_FINALIZING)); + EVBASE_RELEASE_LOCK(base, th_base_lock); + base->current_event = NULL; + evcb->evcb_cb_union.evcb_cbfinalize(evcb, evcb->evcb_arg); + break; default: EVUTIL_ASSERT(0); } @@ -1822,7 +1839,7 @@ event_base_once(struct event_base *base, evutil_socket_t fd, short events, eonce->cb = callback; eonce->arg = arg; - if (events == EV_TIMEOUT) { + if ((events & (EV_TIMEOUT|EV_SIGNAL|EV_READ|EV_WRITE)) == EV_TIMEOUT) { evtimer_assign(&eonce->ev, base, event_once_cb, eonce); if (tv == NULL || ! evutil_timerisset(tv)) { @@ -1972,7 +1989,9 @@ event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)( void event_free(struct event *ev) { - event_debug_assert_is_setup_(ev); + /* This is disabled, so that events which have been finalized be a + * valid target for event_free(). That's */ + // event_debug_assert_is_setup_(ev); /* make sure that this event won't be coming back to haunt us. */ event_del(ev); @@ -1990,6 +2009,108 @@ event_debug_unassign(struct event *ev) ev->ev_flags &= ~EVLIST_INIT; } +#define EVENT_FINALIZE_FREE_ 0x10000 +static void +event_finalize_nolock_(struct event_base *base, unsigned flags, struct event *ev, event_finalize_callback_fn cb) +{ + uint8_t closure = (flags & EVENT_FINALIZE_FREE_) ? + EV_CLOSURE_EVENT_FINALIZE_FREE : EV_CLOSURE_EVENT_FINALIZE; + + event_del_nolock_(ev, EVENT_DEL_NOBLOCK); + ev->ev_closure = closure; + ev->ev_evcallback.evcb_cb_union.evcb_evfinalize = cb; + event_active_nolock_(ev, EV_FINALIZE, 1); + ev->ev_flags |= EVLIST_FINALIZING; +} + +static void +event_finalize_impl_(unsigned flags, struct event *ev, event_finalize_callback_fn cb) +{ + struct event_base *base = ev->ev_base; + if (EVUTIL_FAILURE_CHECK(!base)) { + event_warnx("%s: event has no event_base set.", __func__); + return; + } + + EVBASE_ACQUIRE_LOCK(base, th_base_lock); + event_finalize_nolock_(base, flags, ev, cb); + EVBASE_RELEASE_LOCK(base, th_base_lock); +} + +void +event_finalize(unsigned flags, struct event *ev, event_finalize_callback_fn cb) +{ + return event_finalize_impl_(flags, ev, cb); +} + +void +event_free_finalize(unsigned flags, struct event *ev, event_finalize_callback_fn cb) +{ + return event_finalize_impl_(flags|EVENT_FINALIZE_FREE_, ev, cb); +} + +void +event_callback_finalize_nolock_(struct event_base *base, unsigned flags, struct event_callback *evcb, void (*cb)(struct event_callback *, void *)) +{ + struct event *ev = NULL; + if (evcb->evcb_flags & EVLIST_INIT) { + ev = event_callback_to_event(evcb); + event_del_nolock_(ev, EVENT_DEL_NOBLOCK); + } else { + event_callback_cancel_nolock_(base, evcb, 0); /*XXX can this fail?*/ + } + + evcb->evcb_closure = EV_CLOSURE_CB_FINALIZE; + evcb->evcb_cb_union.evcb_cbfinalize = cb; + event_callback_activate_nolock_(base, evcb); /* XXX can this really fail?*/ + evcb->evcb_flags |= EVLIST_FINALIZING; +} + +void +event_callback_finalize_(struct event_base *base, unsigned flags, struct event_callback *evcb, void (*cb)(struct event_callback *, void *)) +{ + EVBASE_ACQUIRE_LOCK(base, th_base_lock); + event_callback_finalize_nolock_(base, flags, evcb, cb); + EVBASE_RELEASE_LOCK(base, th_base_lock); +} + +/** Internal: Finalize all of the n_cbs callbacks in evcbs. The provided + * callback will be invoked on *one of them*, after they have *all* been + * finalized. */ +int +event_callback_finalize_many_(struct event_base *base, int n_cbs, struct event_callback **evcbs, void (*cb)(struct event_callback *, void *)) +{ + int n_pending = 0, i; + + if (base == NULL) + base = current_base; + + EVBASE_ACQUIRE_LOCK(base, th_base_lock); + + event_debug(("%s: %d events finalizing", __func__, n_cbs)); + + /* At most one can be currently executing; the rest we just + * cancel... But we always make sure that the finalize callback + * runs. */ + for (i = 0; i < n_cbs; ++i) { + struct event_callback *evcb = evcbs[i]; + if (evcb == base->current_event) { + event_callback_finalize_nolock_(base, 0, evcb, cb); + ++n_pending; + } else { + event_callback_cancel_nolock_(base, evcb, 0); + } + } + + if (n_pending == 0) { + /* Just do the first one. */ + event_callback_finalize_nolock_(base, 0, evcbs[0], cb); + } + + EVBASE_RELEASE_LOCK(base, th_base_lock); + return 0; +} + /* * Set's the priority of an event - if an event is already scheduled * changing the priority is going to fail. @@ -2258,6 +2379,11 @@ event_add_nolock_(struct event *ev, const struct timeval *tv, EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL)); + if (ev->ev_flags & EVLIST_FINALIZING) { + /* XXXX debug */ + return (-1); + } + /* * prepare for timeout insertion further below, if we get a * failure on any step, we should not change any state. @@ -2402,8 +2528,8 @@ event_add_nolock_(struct event *ev, const struct timeval *tv, return (res); } -int -event_del(struct event *ev) +static int +event_del_(struct event *ev, int blocking) { int res; @@ -2414,16 +2540,35 @@ event_del(struct event *ev) EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock); - res = event_del_nolock_(ev); + res = event_del_nolock_(ev, blocking); EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock); return (res); } -/* Helper for event_del: always called with th_base_lock held. */ int -event_del_nolock_(struct event *ev) +event_del(struct event *ev) +{ + return event_del_(ev, EVENT_DEL_AUTOBLOCK); +} + +int +event_del_block(struct event *ev) +{ + return event_del_(ev, EVENT_DEL_BLOCK); +} + +int +event_del_noblock(struct event *ev) +{ + return event_del_(ev, EVENT_DEL_NOBLOCK); +} + +/* Helper for event_del: always called with th_base_lock held. + * DOCUMENT blocking */ +int +event_del_nolock_(struct event *ev, int blocking) { struct event_base *base; int res = 0, notify = 0; @@ -2437,6 +2582,13 @@ event_del_nolock_(struct event *ev) EVENT_BASE_ASSERT_LOCKED(ev->ev_base); + if (blocking != EVENT_DEL_EVEN_IF_FINALIZING) { + if (ev->ev_flags & EVLIST_FINALIZING) { + /* XXXX Debug */ + return 0; + } + } + /* If the main thread is currently executing this event's callback, * and we are not the main thread, then we want to wait until the * callback is done before we start removing the event. That way, @@ -2444,8 +2596,10 @@ event_del_nolock_(struct event *ev) * user-supplied argument. */ base = ev->ev_base; #ifndef EVENT__DISABLE_THREAD_SUPPORT - if (base->current_event == event_to_event_callback(ev) && - !EVBASE_IN_THREAD(base)) { + if (blocking != EVENT_DEL_NOBLOCK && + base->current_event == event_to_event_callback(ev) && + !EVBASE_IN_THREAD(base) && + (blocking == EVENT_DEL_BLOCK || !(ev->ev_events & EV_FINALIZE))) { ++base->current_event_waiters; EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock); } @@ -2528,6 +2682,11 @@ event_active_nolock_(struct event *ev, int res, short ncalls) base = ev->ev_base; EVENT_BASE_ASSERT_LOCKED(base); + if (ev->ev_flags & EVLIST_FINALIZING) { + /* XXXX debug */ + return; + } + switch ((ev->ev_flags & (EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) { default: case EVLIST_ACTIVE|EVLIST_ACTIVE_LATER: @@ -2605,6 +2764,9 @@ event_callback_activate_nolock_(struct event_base *base, { int r = 1; + if (evcb->evcb_flags & EVLIST_FINALIZING) + return 0; + switch (evcb->evcb_flags & (EVLIST_ACTIVE|EVLIST_ACTIVE_LATER)) { default: EVUTIL_ASSERT(0); @@ -2652,17 +2814,21 @@ event_callback_cancel_(struct event_base *base, { int r; EVBASE_ACQUIRE_LOCK(base, th_base_lock); - r = event_callback_cancel_nolock_(base, evcb); + r = event_callback_cancel_nolock_(base, evcb, 0); EVBASE_RELEASE_LOCK(base, th_base_lock); return r; } int event_callback_cancel_nolock_(struct event_base *base, - struct event_callback *evcb) + struct event_callback *evcb, int even_if_finalizing) { + if ((evcb->evcb_flags & EVLIST_FINALIZING) && !even_if_finalizing) + return 0; + if (evcb->evcb_flags & EVLIST_INIT) - return event_del_nolock_(event_callback_to_event(evcb)); + return event_del_nolock_(event_callback_to_event(evcb), + even_if_finalizing ? EVENT_DEL_EVEN_IF_FINALIZING : EVENT_DEL_AUTOBLOCK); switch ((evcb->evcb_flags & (EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) { default: @@ -2783,7 +2949,7 @@ timeout_process(struct event_base *base) break; /* delete this event from the I/O queues */ - event_del_nolock_(ev); + event_del_nolock_(ev, EVENT_DEL_NOBLOCK); event_debug(("timeout_process: event: %p, call %p", ev, ev->ev_callback)); diff --git a/include/event2/event.h b/include/event2/event.h index 7b952278..3dcf440d 100644 --- a/include/event2/event.h +++ b/include/event2/event.h @@ -829,7 +829,16 @@ int event_base_got_break(struct event_base *); */ #define EV_PERSIST 0x10 /** Select edge-triggered behavior, if supported by the backend. */ -#define EV_ET 0x20 +#define EV_ET 0x20 +/** + * If this option is provided, then event_del() will not block in one thread + * while waiting for the event callback to complete in another thread. + * + * To use this option safely, you may need to use event_finalize() or + * event_free_finalize() in order to safely tear down an event in a + * multithreaded application. See those functions for more information. + **/ +#define EV_FINALIZE 0x40 /**@}*/ /** @@ -999,6 +1008,39 @@ int event_assign(struct event *, struct event_base *, evutil_socket_t, short, ev void event_free(struct event *); /** + Callback type for event_finalize and event_free_finalize(). + **/ +typedef void (*event_finalize_callback_fn)(struct event *, void *); +/** + @name Finalization functions + + These functions are used to safely tear down an event in a multithreaded + application. If you construct your events with EV_FINALIZE to avoid + deadlocks, you will need a way to remove an event in the certainty that + it will definitely not be running its callback when you deallocate it + and its callback argument. + + To do this, call one of event_finalize() or event_free_finalize with + 0 for its first argument, the event to tear down as its second argument, + and a callback function as its third argument. The callback will be + invoked as part of the event loop, with the event's priority. + + After you call a finalizer function, event_add() and event_active() will + no longer work on the event, and event_del() will produce a no-op. You + must not try to change the event's fields with event_assign() or + event_set() while the finalize callback is in progress. Once the + callback has been invoked, you should treat the event structure as + containing uninitialized memory. + + The event_free_finalize() function frees the event after it's finalized; + event_finalize() does not. + */ +/**@{*/ +void event_finalize(unsigned, struct event *, event_finalize_callback_fn); +void event_free_finalize(unsigned, struct event *, event_finalize_callback_fn); +/**@}*/ + +/** Schedule a one-time event The function event_base_once() is similar to event_new(). However, it @@ -1071,6 +1113,18 @@ int event_remove_timer(struct event *ev); */ int event_del(struct event *); +/** + As event_del(), but never blocks while the event's callback is running + in another thread, even if the event was constructed without the + EV_FINALIZE flag. + */ +int event_del_noblock(struct event *ev); +/** + As event_del(), but always blocks while the event's callback is running + in another thread, even if the event was constructed with the + EV_FINALIZE flag. + */ +int event_del_block(struct event *ev); /** Make an event active. diff --git a/include/event2/event_struct.h b/include/event2/event_struct.h index 8d042846..ad2403ec 100644 --- a/include/event2/event_struct.h +++ b/include/event2/event_struct.h @@ -60,9 +60,10 @@ extern "C" { #define EVLIST_ACTIVE 0x08 #define EVLIST_INTERNAL 0x10 #define EVLIST_ACTIVE_LATER 0x20 +#define EVLIST_FINALIZING 0x40 #define EVLIST_INIT 0x80 -#define EVLIST_ALL 0xbf +#define EVLIST_ALL 0xff /* Fix so that people don't have to run with <sys/queue.h> */ #ifndef TAILQ_ENTRY @@ -101,15 +102,20 @@ struct name { \ } #endif /* !LIST_HEAD */ +struct event; + struct event_callback { + /* DOCUMENT all these fields */ TAILQ_ENTRY(event_callback) evcb_active_next; short evcb_flags; ev_uint8_t evcb_pri; /* smaller numbers are higher priority */ ev_uint8_t evcb_closure; /* allows us to adopt for different types of events */ union { - void (*evcb_callback)(evutil_socket_t, short, void *arg); - void (*evcb_selfcb)(struct event_callback *, void *arg); + void (*evcb_callback)(evutil_socket_t, short, void *); + void (*evcb_selfcb)(struct event_callback *, void *); + void (*evcb_evfinalize)(struct event *, void *); + void (*evcb_cbfinalize)(struct event_callback *, void *); } evcb_cb_union; void *evcb_arg; }; diff --git a/test/include.am b/test/include.am index 324df347..1648dcc3 100644 --- a/test/include.am +++ b/test/include.am @@ -36,6 +36,7 @@ endif noinst_HEADERS+= \ test/regress.h \ + test/regress_thread.h \ test/tinytest.h \ test/tinytest_local.h \ test/tinytest_macros.h @@ -78,6 +79,7 @@ test_regress_SOURCES = \ test/regress_bufferevent.c \ test/regress_dns.c \ test/regress_et.c \ + test/regress_finalize.c \ test/regress_http.c \ test/regress_listener.c \ test/regress_main.c \ diff --git a/test/regress.h b/test/regress.h index 449a59b0..9aa5df4b 100644 --- a/test/regress.h +++ b/test/regress.h @@ -37,6 +37,7 @@ extern "C" { extern struct testcase_t main_testcases[]; extern struct testcase_t evtag_testcases[]; extern struct testcase_t evbuffer_testcases[]; +extern struct testcase_t finalize_testcases[]; extern struct testcase_t bufferevent_testcases[]; extern struct testcase_t bufferevent_iocp_testcases[]; extern struct testcase_t util_testcases[]; diff --git a/test/regress_finalize.c b/test/regress_finalize.c new file mode 100644 index 00000000..44d0bcba --- /dev/null +++ b/test/regress_finalize.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2013 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "event2/event-config.h" +#include "tinytest.h" +#include "tinytest_macros.h" +#include <stdlib.h> + +#include "event2/event.h" +#include "event2/util.h" +#include "event-internal.h" +#include "defer-internal.h" + +#include "regress.h" +#include "regress_thread.h" + +static void +timer_callback(evutil_socket_t fd, short what, void *arg) +{ + int *int_arg = arg; + *int_arg += 1; + (void)fd; + (void)what; +} +static void +simple_callback(struct event_callback *evcb, void *arg) +{ + int *int_arg = arg; + *int_arg += 1; + (void)evcb; +} +static void +event_finalize_callback_1(struct event *ev, void *arg) +{ + int *int_arg = arg; + *int_arg += 100; + (void)ev; +} +static void +callback_finalize_callback_1(struct event_callback *evcb, void *arg) +{ + int *int_arg = arg; + *int_arg += 100; + (void)evcb; +} + + +static void +test_fin_cb_invoked(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *ev; + struct event ev2; + struct event_callback evcb; + int cb_called = 0; + int ev_called = 0; + + const struct timeval ten_sec = {10,0}; + + event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called); + ev = evtimer_new(base, timer_callback, &ev_called); + /* Just finalize them; don't bother adding. */ + event_free_finalize(0, ev, event_finalize_callback_1); + event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1); + + event_base_dispatch(base); + + tt_int_op(cb_called, ==, 100); + tt_int_op(ev_called, ==, 100); + + ev_called = cb_called = 0; + event_base_assert_ok_(base); + + /* Now try it when they're active. (actually, don't finalize: make + * sure activation can happen! */ + ev = evtimer_new(base, timer_callback, &ev_called); + event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called); + + event_active(ev, EV_TIMEOUT, 1); + event_callback_activate_(base, &evcb); + + event_base_dispatch(base); + tt_int_op(cb_called, ==, 1); + tt_int_op(ev_called, ==, 1); + + ev_called = cb_called = 0; + event_base_assert_ok_(base); + + /* Great, it worked. Now activate and finalize and make sure only + * finalizing happens. */ + event_active(ev, EV_TIMEOUT, 1); + event_callback_activate_(base, &evcb); + event_free_finalize(0, ev, event_finalize_callback_1); + event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1); + + event_base_dispatch(base); + tt_int_op(cb_called, ==, 100); + tt_int_op(ev_called, ==, 100); + + ev_called = 0; + + event_base_assert_ok_(base); + + /* Okay, now add but don't have it become active, and make sure *that* + * works. */ + ev = evtimer_new(base, timer_callback, &ev_called); + event_add(ev, &ten_sec); + event_free_finalize(0, ev, event_finalize_callback_1); + + event_base_dispatch(base); + tt_int_op(ev_called, ==, 100); + + ev_called = 0; + event_base_assert_ok_(base); + + /* Now try adding and deleting after finalizing. */ + ev = evtimer_new(base, timer_callback, &ev_called); + evtimer_assign(&ev2, base, timer_callback, &ev_called); + event_add(ev, &ten_sec); + event_free_finalize(0, ev, event_finalize_callback_1); + event_finalize(0, &ev2, event_finalize_callback_1); + + event_add(&ev2, &ten_sec); + event_del(ev); + event_active(&ev2, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(ev_called, ==, 200); + + event_base_assert_ok_(base); + +end: + ; +} + +static void * +tfff_malloc(size_t n) +{ + return malloc(n); +} +static void *tfff_p1=NULL, *tfff_p2=NULL; +static int tfff_p1_freed=0, tfff_p2_freed=0; +static void +tfff_free(void *p) +{ + if (! p) + return; + if (p == tfff_p1) + ++tfff_p1_freed; + if (p == tfff_p2) + ++tfff_p2_freed; + free(p); +} +static void * +tfff_realloc(void *p, size_t sz) +{ + return realloc(p,sz); +} + +static void +test_fin_free_finalize(void *arg) +{ + struct event_base *base = NULL; + + struct event *ev, *ev2; + int ev_called = 0; + int ev2_called = 0; + + (void)arg; + + event_set_mem_functions(tfff_malloc, tfff_realloc, tfff_free); + + base = event_base_new(); + + ev = evtimer_new(base, timer_callback, &ev_called); + ev2 = evtimer_new(base, timer_callback, &ev2_called); + tfff_p1 = ev; + tfff_p2 = ev2; + event_free_finalize(0, ev, event_finalize_callback_1); + event_finalize(0, ev2, event_finalize_callback_1); + + event_base_dispatch(base); + + tt_int_op(ev_called, ==, 100); + tt_int_op(ev2_called, ==, 100); + + event_base_assert_ok_(base); + tt_int_op(tfff_p1_freed, ==, 1); + tt_int_op(tfff_p2_freed, ==, 0); + + event_free(ev2); /* XXXX Right now, this fails under debug mode, since + * the event has been torn down, and therefore can't + * be a valid target for event_free. */ + +end: + if (base) + event_base_free(base); +} + +/* For test_fin_within_cb */ +struct event_and_count { + struct event *ev; + struct event *ev2; + int count; +}; +static void +event_finalize_callback_2(struct event *ev, void *arg) +{ + struct event_and_count *evc = arg; + evc->count += 100; + event_free(ev); +} +static void +timer_callback_2(evutil_socket_t fd, short what, void *arg) +{ + struct event_and_count *evc = arg; + event_finalize(0, evc->ev, event_finalize_callback_2); + event_finalize(0, evc->ev2, event_finalize_callback_2); + ++ evc->count; + (void)fd; + (void)what; +} + +static void +test_fin_within_cb(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event_and_count evc1, evc2; + evc1.count = evc2.count = 0; + evc2.ev2 = evc1.ev = evtimer_new(base, timer_callback_2, &evc1); + evc1.ev2 = evc2.ev = evtimer_new(base, timer_callback_2, &evc2); + + /* Activate both. The first one will have its callback run, which + * will finalize both of them, preventing the second one's callback + * from running. */ + event_active(evc1.ev, EV_TIMEOUT, 1); + event_active(evc2.ev, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(evc1.count, ==, 101); + tt_int_op(evc2.count, ==, 100); + + event_base_assert_ok_(base); + /* Now try with EV_PERSIST events. */ + evc1.count = evc2.count = 0; + evc2.ev2 = evc1.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc1); + evc1.ev2 = evc2.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc2); + + event_active(evc1.ev, EV_TIMEOUT, 1); + event_active(evc2.ev, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(evc1.count, ==, 101); + tt_int_op(evc2.count, ==, 100); + + event_base_assert_ok_(base); +end: + ; +} + +#if 0 +static void +timer_callback_3(evutil_socket_t *fd, short what, void *arg) +{ + (void)fd; + (void)what; + +} +static void +test_fin_many(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *ev1, *ev2; + struct event_callback evcb1, evcb2; + int ev1_count = 0, ev2_count = 0; + int evcb1_count = 0, evcb2_count = 0; + struct event_callback *array[4]; + + int n; + + /* First attempt: call finalize_many with no events running */ + ev1 = evtimer_new(base, timer_callback, &ev1_count); + ev1 = evtimer_new(base, timer_callback, &ev2_count); + event_deferred_cb_init_(&evcb1, 0, simple_callback, &evcb1_called); + event_deferred_cb_init_(&evcb2, 0, simple_callback, &evcb2_called); + array[0] = &ev1->ev_evcallback; + array[1] = &ev2->ev_evcallback; + array[2] = &evcb1; + array[3] = &evcb2; + + + + n = event_callback_finalize_many(base, 4, array, + callback_finalize_callback_1); + +} +#endif + + +#define TEST(name, flags) \ + { #name, test_fin_##name, (flags), &basic_setup, NULL } + +struct testcase_t finalize_testcases[] = { + + TEST(cb_invoked, TT_FORK|TT_NEED_BASE), + TEST(free_finalize, TT_FORK), + TEST(within_cb, TT_FORK|TT_NEED_BASE), +// TEST(many, TT_FORK|TT_NEED_BASE), + + + END_OF_TESTCASES +}; + diff --git a/test/regress_main.c b/test/regress_main.c index a47a78ae..55323569 100644 --- a/test/regress_main.c +++ b/test/regress_main.c @@ -371,6 +371,7 @@ struct testgroup_t testgroups[] = { { "main/", main_testcases }, { "heap/", minheap_testcases }, { "et/", edgetriggered_testcases }, + { "finalize/", finalize_testcases }, { "evbuffer/", evbuffer_testcases }, { "signal/", signal_testcases }, { "util/", util_testcases }, diff --git a/test/regress_thread.c b/test/regress_thread.c index 809cbd86..612bf1d6 100644 --- a/test/regress_thread.c +++ b/test/regress_thread.c @@ -64,24 +64,7 @@ #include "regress.h" #include "tinytest_macros.h" #include "time-internal.h" - -#ifdef EVENT__HAVE_PTHREADS -#define THREAD_T pthread_t -#define THREAD_FN void * -#define THREAD_RETURN() return (NULL) -#define THREAD_START(threadvar, fn, arg) \ - pthread_create(&(threadvar), NULL, fn, arg) -#define THREAD_JOIN(th) pthread_join(th, NULL) -#else -#define THREAD_T HANDLE -#define THREAD_FN unsigned __stdcall -#define THREAD_RETURN() return (0) -#define THREAD_START(threadvar, fn, arg) do { \ - uintptr_t threadhandle = _beginthreadex(NULL,0,fn,(arg),0,NULL); \ - (threadvar) = (HANDLE) threadhandle; \ - } while (0) -#define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE) -#endif +#include "regress_thread.h" struct cond_wait { void *lock; diff --git a/test/regress_thread.h b/test/regress_thread.h new file mode 100644 index 00000000..831b51e5 --- /dev/null +++ b/test/regress_thread.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef REGRESS_THREAD_H_INCLUDED_ +#define REGRESS_THREAD_H_INCLUDED_ + +#ifdef EVENT__HAVE_PTHREADS +#define THREAD_T pthread_t +#define THREAD_FN void * +#define THREAD_RETURN() return (NULL) +#define THREAD_START(threadvar, fn, arg) \ + pthread_create(&(threadvar), NULL, fn, arg) +#define THREAD_JOIN(th) pthread_join(th, NULL) +#else +#define THREAD_T HANDLE +#define THREAD_FN unsigned __stdcall +#define THREAD_RETURN() return (0) +#define THREAD_START(threadvar, fn, arg) do { \ + uintptr_t threadhandle = _beginthreadex(NULL,0,fn,(arg),0,NULL); \ + (threadvar) = (HANDLE) threadhandle; \ + } while (0) +#define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE) +#endif + +#endif |