summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2013-03-28 14:13:19 -0400
committerNick Mathewson <nickm@torproject.org>2013-04-26 12:18:07 -0400
commit8eedeabe50563ac6d679700ca9856a67875ceb39 (patch)
tree9a34aec1f7d11a79dda2386f62f3d690daa36ab9
parent2fad0f3d52c6c7511231b1b2eced484306835a52 (diff)
downloadlibevent-8eedeabe50563ac6d679700ca9856a67875ceb39.tar.gz
Implement event_finalize() and related functions to avoid certain deadlocks
-rw-r--r--event-internal.h17
-rw-r--r--event.c210
-rw-r--r--include/event2/event.h56
-rw-r--r--include/event2/event_struct.h12
-rw-r--r--test/include.am2
-rw-r--r--test/regress.h1
-rw-r--r--test/regress_finalize.c342
-rw-r--r--test/regress_main.c1
-rw-r--r--test/regress_thread.c19
-rw-r--r--test/regress_thread.h48
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);
diff --git a/event.c b/event.c
index 9286c36f..bad277ce 100644
--- a/event.c
+++ b/event.c
@@ -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