tevent: split out tevent_common_invoke_timer_handler()
[metze/samba/wip.git] / lib / tevent / tevent_timed.c
index 78c8a24511a8002c8953136147652ad082d3318b..d460e7001dcae0601915bcc867eacf375278e197 100644 (file)
@@ -4,20 +4,24 @@
    common events code for timed events
 
    Copyright (C) Andrew Tridgell       2003-2006
-   Copyright (C) Stefan Metzmacher     2005
-   
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 3 of the License, or
-   (at your option) any later version.
-   
-   This program is distributed in the hope that it will be useful,
+   Copyright (C) Stefan Metzmacher     2005-2009
+
+     ** NOTE! The following LGPL license applies to the tevent
+     ** library. This does NOT imply that all of Samba is released
+     ** under the LGPL
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 3 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-   
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "replace.h"
@@ -32,7 +36,7 @@
   Return 0 if tv1 == tv2
   Return 1 if tv1 > tv2
 */
-static int ev_timeval_compare(const struct timeval *tv1, const struct timeval *tv2)
+int tevent_timeval_compare(const struct timeval *tv1, const struct timeval *tv2)
 {
        if (tv1->tv_sec  > tv2->tv_sec)  return 1;
        if (tv1->tv_sec  < tv2->tv_sec)  return -1;
@@ -44,7 +48,7 @@ static int ev_timeval_compare(const struct timeval *tv1, const struct timeval *t
 /**
   return a zero timeval
 */
-static struct timeval ev_timeval_zero(void)
+struct timeval tevent_timeval_zero(void)
 {
        struct timeval tv;
        tv.tv_sec = 0;
@@ -55,7 +59,7 @@ static struct timeval ev_timeval_zero(void)
 /**
   return a timeval for the current time
 */
-static struct timeval ev_timeval_current(void)
+struct timeval tevent_timeval_current(void)
 {
        struct timeval tv;
        gettimeofday(&tv, NULL);
@@ -65,7 +69,7 @@ static struct timeval ev_timeval_current(void)
 /**
   return a timeval struct with the given elements
 */
-static struct timeval ev_timeval_set(uint32_t secs, uint32_t usecs)
+struct timeval tevent_timeval_set(uint32_t secs, uint32_t usecs)
 {
        struct timeval tv;
        tv.tv_sec = secs;
@@ -78,12 +82,12 @@ static struct timeval ev_timeval_set(uint32_t secs, uint32_t usecs)
   if tv1 comes after tv2, then return a zero timeval
   (this is *tv2 - *tv1)
 */
-static struct timeval ev_timeval_until(const struct timeval *tv1,
-                                       const struct timeval *tv2)
+struct timeval tevent_timeval_until(const struct timeval *tv1,
+                                   const struct timeval *tv2)
 {
        struct timeval t;
-       if (ev_timeval_compare(tv1, tv2) >= 0) {
-               return ev_timeval_zero();
+       if (tevent_timeval_compare(tv1, tv2) >= 0) {
+               return tevent_timeval_zero();
        }
        t.tv_sec = tv2->tv_sec - tv1->tv_sec;
        if (tv1->tv_usec > tv2->tv_usec) {
@@ -98,85 +102,289 @@ static struct timeval ev_timeval_until(const struct timeval *tv1,
 /**
   return true if a timeval is zero
 */
-bool ev_timeval_is_zero(const struct timeval *tv)
+bool tevent_timeval_is_zero(const struct timeval *tv)
 {
        return tv->tv_sec == 0 && tv->tv_usec == 0;
 }
 
+struct timeval tevent_timeval_add(const struct timeval *tv, uint32_t secs,
+                                 uint32_t usecs)
+{
+       struct timeval tv2 = *tv;
+       tv2.tv_sec += secs;
+       tv2.tv_usec += usecs;
+       tv2.tv_sec += tv2.tv_usec / 1000000;
+       tv2.tv_usec = tv2.tv_usec % 1000000;
+
+       return tv2;
+}
+
+/**
+  return a timeval in the future with a specified offset
+*/
+struct timeval tevent_timeval_current_ofs(uint32_t secs, uint32_t usecs)
+{
+       struct timeval tv = tevent_timeval_current();
+       return tevent_timeval_add(&tv, secs, usecs);
+}
+
 /*
   destroy a timed event
 */
 static int tevent_common_timed_destructor(struct tevent_timer *te)
 {
-       if (te->event_ctx) {
-               DLIST_REMOVE(te->event_ctx->timer_events, te);
+       if (te->destroyed) {
+               tevent_common_check_double_free(te, "tevent_timer double free");
+               goto done;
        }
+       te->destroyed = true;
+
+       if (te->event_ctx == NULL) {
+               return 0;
+       }
+
+       tevent_debug(te->event_ctx, TEVENT_DEBUG_TRACE,
+                    "Destroying timer event %p \"%s\"\n",
+                    te, te->handler_name);
+
+       if (te->event_ctx->last_zero_timer == te) {
+               te->event_ctx->last_zero_timer = DLIST_PREV(te);
+       }
+       DLIST_REMOVE(te->event_ctx->timer_events, te);
+
+       te->event_ctx = NULL;
+
+done:
+       if (te->busy) {
+               return -1;
+       }
+
        return 0;
 }
 
-static int tevent_common_timed_deny_destructor(struct tevent_timer *te)
+static void tevent_common_insert_timer(struct tevent_context *ev,
+                                      struct tevent_timer *te,
+                                      bool optimize_zero)
 {
-       return -1;
+       struct tevent_timer *prev_te = NULL;
+
+       if (te->destroyed) {
+               tevent_abort(ev, "tevent_timer use after free");
+               return;
+       }
+
+       /* keep the list ordered */
+       if (optimize_zero && tevent_timeval_is_zero(&te->next_event)) {
+               /*
+                * Some callers use zero tevent_timer
+                * instead of tevent_immediate events.
+                *
+                * As these can happen very often,
+                * we remember the last zero timer
+                * in the list.
+                */
+               prev_te = ev->last_zero_timer;
+               ev->last_zero_timer = te;
+       } else {
+               struct tevent_timer *cur_te;
+
+               /*
+                * we traverse the list from the tail
+                * because it's much more likely that
+                * timers are added at the end of the list
+                */
+               for (cur_te = DLIST_TAIL(ev->timer_events);
+                    cur_te != NULL;
+                    cur_te = DLIST_PREV(cur_te))
+               {
+                       int ret;
+
+                       /*
+                        * if the new event comes before the current
+                        * we continue searching
+                        */
+                       ret = tevent_timeval_compare(&te->next_event,
+                                                    &cur_te->next_event);
+                       if (ret < 0) {
+                               continue;
+                       }
+
+                       break;
+               }
+
+               prev_te = cur_te;
+       }
+
+       DLIST_ADD_AFTER(ev->timer_events, te, prev_te);
 }
 
 /*
   add a timed event
   return NULL on failure (memory allocation error)
 */
-struct tevent_timer *tevent_common_add_timer(struct tevent_context *ev, TALLOC_CTX *mem_ctx,
+static struct tevent_timer *tevent_common_add_timer_internal(
+                                       struct tevent_context *ev,
+                                       TALLOC_CTX *mem_ctx,
+                                       struct timeval next_event,
+                                       tevent_timer_handler_t handler,
+                                       void *private_data,
+                                       const char *handler_name,
+                                       const char *location,
+                                       bool optimize_zero)
+{
+       struct tevent_timer *te;
+
+       te = talloc(mem_ctx?mem_ctx:ev, struct tevent_timer);
+       if (te == NULL) return NULL;
+
+       *te = (struct tevent_timer) {
+               .event_ctx      = ev,
+               .next_event     = next_event,
+               .handler        = handler,
+               .private_data   = private_data,
+               .handler_name   = handler_name,
+               .location       = location,
+       };
+
+       if (ev->timer_events == NULL) {
+               ev->last_zero_timer = NULL;
+       }
+
+       tevent_common_insert_timer(ev, te, optimize_zero);
+
+       talloc_set_destructor(te, tevent_common_timed_destructor);
+
+       tevent_debug(ev, TEVENT_DEBUG_TRACE,
+                    "Added timed event \"%s\": %p\n",
+                    handler_name, te);
+       return te;
+}
+
+struct tevent_timer *tevent_common_add_timer(struct tevent_context *ev,
+                                            TALLOC_CTX *mem_ctx,
                                             struct timeval next_event,
                                             tevent_timer_handler_t handler,
                                             void *private_data,
                                             const char *handler_name,
                                             const char *location)
 {
-       struct tevent_timer *te, *last_te, *cur_te;
+       /*
+        * do not use optimization, there are broken Samba
+        * versions which use tevent_common_add_timer()
+        * without using tevent_common_loop_timer_delay(),
+        * it just uses DLIST_REMOVE(ev->timer_events, te)
+        * and would leave ev->last_zero_timer behind.
+        */
+       return tevent_common_add_timer_internal(ev, mem_ctx, next_event,
+                                               handler, private_data,
+                                               handler_name, location,
+                                               false);
+}
 
-       te = talloc(mem_ctx?mem_ctx:ev, struct tevent_timer);
-       if (te == NULL) return NULL;
+struct tevent_timer *tevent_common_add_timer_v2(struct tevent_context *ev,
+                                               TALLOC_CTX *mem_ctx,
+                                               struct timeval next_event,
+                                               tevent_timer_handler_t handler,
+                                               void *private_data,
+                                               const char *handler_name,
+                                               const char *location)
+{
+       /*
+        * Here we turn on last_zero_timer optimization
+        */
+       return tevent_common_add_timer_internal(ev, mem_ctx, next_event,
+                                               handler, private_data,
+                                               handler_name, location,
+                                               true);
+}
 
-       te->event_ctx           = ev;
-       te->next_event          = next_event;
-       te->handler             = handler;
-       te->private_data        = private_data;
-       te->handler_name        = handler_name;
-       te->location            = location;
-       te->additional_data     = NULL;
+void tevent_update_timer(struct tevent_timer *te, struct timeval next_event)
+{
+       struct tevent_context *ev = te->event_ctx;
 
-       /* keep the list ordered */
-       last_te = NULL;
-       for (cur_te = ev->timer_events; cur_te; cur_te = cur_te->next) {
-               /* if the new event comes before the current one break */
-               if (ev_timeval_compare(&te->next_event, &cur_te->next_event) < 0) {
-                       break;
-               }
+       if (ev->last_zero_timer == te) {
+               te->event_ctx->last_zero_timer = DLIST_PREV(te);
+       }
+       DLIST_REMOVE(ev->timer_events, te);
+
+       te->next_event = next_event;
 
-               last_te = cur_te;
+       /*
+        * Not doing the zero_timer optimization. This is for new code
+        * that should know about immediates.
+        */
+       tevent_common_insert_timer(ev, te, false);
+}
+
+_PRIVATE_
+int tevent_common_invoke_timer_handler(struct tevent_timer *te,
+                                      struct timeval current_time,
+                                      bool *removed)
+{
+       if (removed != NULL) {
+               *removed = false;
        }
 
-       DLIST_ADD_AFTER(ev->timer_events, te, last_te);
+       if (te->event_ctx == NULL) {
+               return 0;
+       }
 
-       talloc_set_destructor(te, tevent_common_timed_destructor);
+       /*
+        * We need to remove the timer from the list before calling the
+        * handler because in a semi-async inner event loop called from the
+        * handler we don't want to come across this event again -- vl
+        */
+       if (te->event_ctx->last_zero_timer == te) {
+               te->event_ctx->last_zero_timer = DLIST_PREV(te);
+       }
+       DLIST_REMOVE(te->event_ctx->timer_events, te);
 
-       return te;
-}
+       tevent_debug(te->event_ctx, TEVENT_DEBUG_TRACE,
+                    "Running timer event %p \"%s\"\n",
+                    te, te->handler_name);
 
+       /*
+        * If the timed event was registered for a zero current_time,
+        * then we pass a zero timeval here too! To avoid the
+        * overhead of gettimeofday() calls.
+        *
+        * otherwise we pass the current time
+        */
+       te->busy = true;
+       te->handler(te->event_ctx, te, current_time, te->private_data);
+       te->busy = false;
+
+       tevent_debug(te->event_ctx, TEVENT_DEBUG_TRACE,
+                    "Ending timer event %p \"%s\"\n",
+                    te, te->handler_name);
+
+       te->event_ctx = NULL;
+       talloc_set_destructor(te, NULL);
+       TALLOC_FREE(te);
+
+       if (removed != NULL) {
+               *removed = true;
+       }
+
+       return 0;
+}
 /*
   do a single event loop using the events defined in ev
 
-  return the delay untill the next timed event,
+  return the delay until the next timed event,
   or zero if a timed event was triggered
 */
 struct timeval tevent_common_loop_timer_delay(struct tevent_context *ev)
 {
-       struct timeval current_time = ev_timeval_zero();
+       struct timeval current_time = tevent_timeval_zero();
        struct tevent_timer *te = ev->timer_events;
+       int ret;
 
        if (!te) {
                /* have a default tick time of 30 seconds. This guarantees
                   that code that uses its own timeout checking will be
-                  able to proceeed eventually */
-               return ev_timeval_set(30, 0);
+                  able to proceed eventually */
+               return tevent_timeval_set(30, 0);
        }
 
        /*
@@ -188,13 +396,13 @@ struct timeval tevent_common_loop_timer_delay(struct tevent_context *ev)
         * if there's a delay till the next timed event, we're done
         * with just returning the delay
         */
-       if (!ev_timeval_is_zero(&te->next_event)) {
+       if (!tevent_timeval_is_zero(&te->next_event)) {
                struct timeval delay;
 
-               current_time = ev_timeval_current();
+               current_time = tevent_timeval_current();
 
-               delay = ev_timeval_until(&current_time, &te->next_event);
-               if (!ev_timeval_is_zero(&delay)) {
+               delay = tevent_timeval_until(&current_time, &te->next_event);
+               if (!tevent_timeval_is_zero(&delay)) {
                        return delay;
                }
        }
@@ -202,30 +410,11 @@ struct timeval tevent_common_loop_timer_delay(struct tevent_context *ev)
        /*
         * ok, we have a timed event that we'll process ...
         */
+       ret = tevent_common_invoke_timer_handler(te, current_time, NULL);
+       if (ret != 0) {
+               tevent_abort(ev, "tevent_common_invoke_timer_handler() failed");
+       }
 
-       /* deny the handler to free the event */
-       talloc_set_destructor(te, tevent_common_timed_deny_destructor);
-
-       /* We need to remove the timer from the list before calling the
-        * handler because in a semi-async inner event loop called from the
-        * handler we don't want to come across this event again -- vl */
-       DLIST_REMOVE(ev->timer_events, te);
-
-       /*
-        * If the timed event was registered for a zero current_time,
-        * then we pass a zero timeval here too! To avoid the
-        * overhead of gettimeofday() calls.
-        *
-        * otherwise we pass the current time
-        */
-       te->handler(ev, te, current_time, te->private_data);
-
-       /* The destructor isn't necessary anymore, we've already removed the
-        * event from the list. */
-       talloc_set_destructor(te, NULL);
-
-       talloc_free(te);
-
-       return ev_timeval_zero();
+       return tevent_timeval_zero();
 }