]> granicus.if.org Git - libevent/commitdiff
Implement event_finalize() and related functions to avoid certain deadlocks
authorNick Mathewson <nickm@torproject.org>
Thu, 28 Mar 2013 18:13:19 +0000 (14:13 -0400)
committerNick Mathewson <nickm@torproject.org>
Fri, 26 Apr 2013 16:18:07 +0000 (12:18 -0400)
event-internal.h
event.c
include/event2/event.h
include/event2/event_struct.h
test/include.am
test/regress.h
test/regress_finalize.c [new file with mode: 0644]
test/regress_main.c
test/regress_thread.c
test/regress_thread.h [new file with mode: 0644]

index 614f5d800dc69a39d70d23006fc2621db71a765f..07d29e3291665ff3c17432086fd0f8229e5b4377 100644 (file)
@@ -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 9286c36fba4d62dfb07ed7d5c38680a0260dd43a..bad277ce5f29aff75f40f099a2b2c72a63b0473b 100644 (file)
--- 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));
index 7b952278547daf81d9abc70bdefa2825393876dc..3dcf440d99e633dc328719e43b730bc107fad24a 100644 (file)
@@ -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
 /**@}*/
 
 /**
@@ -998,6 +1007,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
 
@@ -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.
index 8d0428462588e7e694ac1772da4fff9f6fae56a7..ad2403ec67a70c3a506c0cf51fe8ff90e899ea56 100644 (file)
@@ -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;
 };
index 324df347534abda11c55e3d0c7893b32f42757a2..1648dcc3cbd723403478af66ca15ffd3787421fa 100644 (file)
@@ -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                             \
index 449a59b0080bc450cc14c732e3df37841ee7c9b2..9aa5df4b3da3bcc382435addf74d79d94948df78 100644 (file)
@@ -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 (file)
index 0000000..44d0bcb
--- /dev/null
@@ -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
+};
+
index a47a78ae5b3e4efd6cb787d26acfa2823b62f9ee..55323569968a1b8f6bce99539e547ed29e692bd2 100644 (file)
@@ -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 },
index 809cbd864978b3d9c398d9e2ff351bff7f91c91b..612bf1d6111fafdeb0be04597e7f5890a833f8e4 100644 (file)
 #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 (file)
index 0000000..831b51e
--- /dev/null
@@ -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