/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2005 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* 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 end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 KANNEL GROUP OR ITS CONTRIBUTORS
* 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see .
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
/*
* timers.c - timers and set of timers, mainly for WTP.
*
* See timers.h for a description of the interface.
*/
#include
#include "gwlib/gwlib.h"
#include "wap_events.h"
#include "timers.h"
/*
* Active timers are stored in a TimerHeap. It is a partially ordered
* array. Each element i is the child of element i/2 (rounded down),
* and a child never elapses before its parent. The result is that
* element 0, the top of the heap, is always the first timer to
* elapse. The heap is kept in this partial order by all operations on
* it. Maintaining a partial order is much cheaper than maintaining
* a sorted list.
* The array will be resized as needed. The size field is the number
* of elements for which space is reserved, and the len field is the
* number of elements actually used. The elements used will always be
* at tab[0] through tab[len-1].
*/
struct TimerHeap
{
Timer **tab;
long len;
long size;
};
typedef struct TimerHeap TimerHeap;
struct Timerset
{
/*
* This field is set to true when the timer thread should shut down.
*/
volatile sig_atomic_t stopping;
/*
* The entire set is locked for any operation on it. This is
* not as expensive as it sounds because usually each set is
* used by one caller thread and one (internal) timer thread,
* and the timer thread does not wake up very often.
*/
Mutex *mutex;
/*
* Active timers are stored here in a partially ordered structure.
* See the definition of TimerHeap, above, for an explanation.
*/
TimerHeap *heap;
/*
* The thread that watches the top of the heap, and processes
* timers that have elapsed.
*/
long thread;
};
typedef struct Timerset Timerset;
struct Timer
{
/*
* An event is produced on the output list when the
* timer elapses. The timer is not considered to have
* elapsed completely until that pointer has also been
* consumed from this list (by the caller, presumably).
* That is why the timer code sometimes goes back and
* removes a pointer from the output list.
*/
List *output;
/*
* The timer is set to elapse at this time, expressed in
* Unix time format. This field is set to -1 if the timer
* is not active (i.e. in the timer set's heap).
*/
long elapses;
/*
* A duplicate of this event will be put on the output list
* when the timer elapses. It can be NULL if the timer has
* not been started yet.
*/
WAPEvent *event;
/*
* This field is normally NULL, but after the timer elapses
* it points to the event that was put on the output list.
* It is set back to NULL if the event was taken back from
* the list, or if it's confirmed that the event was consumed.
*/
WAPEvent *elapsed_event;
/*
* Index in the timer set's heap. This field is managed by
* the heap operations, and is used to make them faster.
* If this timer is not in the heap, this field is -1.
*/
long index;
};
/*
* Currently we have one timerset (and thus one heap and one thread)
* for all timers. This might change in the future in order to tune
* performance. In that case, it will be necessary to add a "set"
* field to the Timer structure.
*/
static Timerset *timers;
/*
* Used by timer functions to assert that the timer module has been
* intialized.
*/
static int initialized = 0;
/*
* Internal functions
*/
static void abort_elapsed(Timer *timer);
static TimerHeap *heap_create(void);
static void heap_destroy(TimerHeap *heap);
static void heap_delete(TimerHeap *heap, long index);
static int heap_adjust(TimerHeap *heap, long index);
static void heap_insert(TimerHeap *heap, Timer *timer);
static void heap_swap(TimerHeap *heap, long index1, long index2);
static void lock(Timerset *set);
static void unlock(Timerset *set);
static void watch_timers(void *arg); /* The timer thread */
static void elapse_timer(Timer *timer);
void timers_init(void)
{
if (initialized == 0) {
timers = gw_malloc(sizeof(*timers));
timers->mutex = mutex_create();
timers->heap = heap_create();
timers->stopping = 0;
timers->thread = gwthread_create(watch_timers, timers);
}
initialized++;
}
void timers_shutdown(void)
{
if (initialized > 1) {
initialized--;
return;
}
/* Stop all timers. */
if (timers->heap->len > 0)
warning(0, "Timers shutting down with %ld active timers.",
timers->heap->len);
while (timers->heap->len > 0)
gwtimer_stop(timers->heap->tab[0]);
/* Kill timer thread */
timers->stopping = 1;
gwthread_wakeup(timers->thread);
gwthread_join(timers->thread);
initialized = 0;
/* Free resources */
heap_destroy(timers->heap);
mutex_destroy(timers->mutex);
gw_free(timers);
}
Timer *gwtimer_create(List *outputlist)
{
Timer *t;
gw_assert(initialized);
t = gw_malloc(sizeof(*t));
t->elapses = -1;
t->event = NULL;
t->elapsed_event = NULL;
t->index = -1;
t->output = outputlist;
gwlist_add_producer(outputlist);
return t;
}
void gwtimer_destroy(Timer *timer)
{
gw_assert(initialized);
if (timer == NULL)
return;
gwtimer_stop(timer);
gwlist_remove_producer(timer->output);
wap_event_destroy(timer->event);
gw_free(timer);
}
void gwtimer_start(Timer *timer, int interval, WAPEvent *event)
{
int wakeup = 0;
gw_assert(initialized);
gw_assert(timer != NULL);
gw_assert(event != NULL || timer->event != NULL);
lock(timers);
/* Convert to absolute time */
interval += time(NULL);
if (timer->elapses > 0) {
/* Resetting an existing timer. Move it to its new
* position in the heap. */
if (interval < timer->elapses && timer->index == 0)
wakeup = 1;
timer->elapses = interval;
gw_assert(timers->heap->tab[timer->index] == timer);
wakeup |= heap_adjust(timers->heap, timer->index);
} else {
/* Setting a new timer, or resetting an elapsed one.
* First deal with a possible elapse event that may
* still be on the output list. */
abort_elapsed(timer);
/* Then activate the timer. */
timer->elapses = interval;
gw_assert(timer->index < 0);
heap_insert(timers->heap, timer);
wakeup = timer->index == 0; /* Do we have a new top? */
}
if (event != NULL) {
wap_event_destroy(timer->event);
timer->event = event;
}
unlock(timers);
if (wakeup)
gwthread_wakeup(timers->thread);
}
void gwtimer_stop(Timer *timer)
{
gw_assert(initialized);
gw_assert(timer != NULL);
lock(timers);
/*
* If the timer is active, make it inactive and remove it from
* the heap.
*/
if (timer->elapses > 0) {
timer->elapses = -1;
gw_assert(timers->heap->tab[timer->index] == timer);
heap_delete(timers->heap, timer->index);
}
abort_elapsed(timer);
unlock(timers);
}
static void lock(Timerset *set)
{
gw_assert(set != NULL);
mutex_lock(set->mutex);
}
static void unlock(Timerset *set)
{
gw_assert(set != NULL);
mutex_unlock(set->mutex);
}
/*
* Go back and remove this timer's elapse event from the output list,
* to pretend that it didn't elapse after all. This is necessary
* to deal with some races between the timer thread and the caller's
* start/stop actions.
*/
static void abort_elapsed(Timer *timer)
{
long count;
if (timer->elapsed_event == NULL)
return;
count = gwlist_delete_equal(timer->output, timer->elapsed_event);
if (count > 0) {
debug("timers", 0, "Aborting %s timer.",
wap_event_name(timer->elapsed_event->type));
wap_event_destroy(timer->elapsed_event);
}
timer->elapsed_event = NULL;
}
/*
* Create a new timer heap.
*/
static TimerHeap *heap_create(void)
{
TimerHeap *heap;
heap = gw_malloc(sizeof(*heap));
heap->tab = gw_malloc(sizeof(heap->tab[0]));
heap->size = 1;
heap->len = 0;
return heap;
}
static void heap_destroy(TimerHeap *heap)
{
if (heap == NULL)
return;
gw_free(heap->tab);
gw_free(heap);
}
/*
* Remove a timer from the heap. Do this by swapping it with the element
* in the last position, then shortening the heap, then moving the
* swapped element up or down to maintain the partial ordering.
*/
static void heap_delete(TimerHeap *heap, long index)
{
long last;
gw_assert(index >= 0);
gw_assert(index < heap->len);
gw_assert(heap->tab[index]->index == index);
last = heap->len - 1;
heap_swap(heap, index, last);
heap->tab[last]->index = -1;
heap->len--;
if (index != last)
heap_adjust(heap, index);
}
/*
* Add a timer to the heap. Do this by adding it at the end, then
* moving it up or down as necessary to achieve partial ordering.
*/
static void heap_insert(TimerHeap *heap, Timer *timer)
{
heap->len++;
if (heap->len > heap->size) {
heap->tab = gw_realloc(heap->tab,
heap->len * sizeof(heap->tab[0]));
heap->size = heap->len;
}
heap->tab[heap->len - 1] = timer;
timer->index = heap->len - 1;
heap_adjust(heap, timer->index);
}
/*
* Swap two elements of the heap, and update their index fields.
* This is the basic heap operation.
*/
static void heap_swap(TimerHeap *heap, long index1, long index2)
{
Timer *t;
gw_assert(index1 >= 0);
gw_assert(index1 < heap->len);
gw_assert(index2 >= 0);
gw_assert(index2 < heap->len);
if (index1 == index2)
return;
t = heap->tab[index1];
heap->tab[index1] = heap->tab[index2];
heap->tab[index2] = t;
heap->tab[index1]->index = index1;
heap->tab[index2]->index = index2;
}
/*
* The current element has broken the partial ordering of the
* heap (see explanation in the definition of Timerset), and
* it has to be moved up or down until the ordering is restored.
* Return 1 if the timer at the heap's top is now earlier than
* before this operation, otherwise 0.
*/
static int heap_adjust(TimerHeap *heap, long index)
{
Timer *t;
Timer *parent;
long child_index;
/*
* We can assume that the heap was fine before this element's
* elapse time was changed. There are three cases to deal
* with:
* - Element's new elapse time is too small; it should be
* moved toward the top.
* - Element's new elapse time is too large; it should be
* moved toward the bottom.
* - Element's new elapse time still fits here, we don't
* have to do anything.
*/
gw_assert(index >= 0);
gw_assert(index < heap->len);
/* Move to top? */
t = heap->tab[index];
parent = heap->tab[index / 2];
if (t->elapses < parent->elapses) {
/* This will automatically terminate when it reaches
* the top, because in that t == parent. */
do {
heap_swap(heap, index, index / 2);
index = index / 2;
parent = heap->tab[index / 2];
} while (t->elapses < parent->elapses);
/* We're done. Return 1 if we changed the top. */
return index == 0;
}
/* Move to bottom? */
for (; ; ) {
child_index = index * 2;
if (child_index >= heap->len)
return 0; /* Already at bottom */
if (child_index == heap->len - 1) {
/* Only one child */
if (heap->tab[child_index]->elapses < t->elapses)
heap_swap(heap, index, child_index);
break;
}
/* Find out which child elapses first */
if (heap->tab[child_index + 1]->elapses <
heap->tab[child_index]->elapses) {
child_index++;
}
if (heap->tab[child_index]->elapses < t->elapses) {
heap_swap(heap, index, child_index);
index = child_index;
} else {
break;
}
}
return 0;
}
/*
* This timer has elapsed. Do the housekeeping. We have its set locked.
*/
static void elapse_timer(Timer *timer)
{
gw_assert(timer != NULL);
gw_assert(timers != NULL);
/* This must be true because abort_elapsed is always called
* before a timer is activated. */
gw_assert(timer->elapsed_event == NULL);
debug("timers", 0, "%s elapsed.", wap_event_name(timer->event->type));
timer->elapsed_event = wap_event_duplicate(timer->event);
gwlist_produce(timer->output, timer->elapsed_event);
timer->elapses = -1;
}
/*
* Main function for timer thread.
*/
static void watch_timers(void *arg)
{
Timerset *set;
long top_time;
long now;
set = arg;
while (!set->stopping) {
lock(set);
now = time(NULL);
while (set->heap->len > 0 && set->heap->tab[0]->elapses <= now) {
elapse_timer(set->heap->tab[0]);
heap_delete(set->heap, 0);
}
/*
* Now sleep until the next timer elapses. If there isn't one,
* then just sleep very long. We will get woken up if the
* top of the heap changes before we wake.
*/
if (set->heap->len == 0) {
unlock(set);
gwthread_sleep(1000000.0);
} else {
top_time = set->heap->tab[0]->elapses;
unlock(set);
gwthread_sleep(top_time - now);
}
}
}