timers.c
来自「mms client」· C语言 代码 · 共 510 行
C
510 行
/* * timers.c - timers and set of timers, mainly for WTP. * * See timers.h for a description of the interface. */#include <signal.h>#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; list_add_producer(outputlist); return t;}void gwtimer_destroy(Timer *timer){ gw_assert(initialized); if (timer == NULL) return; gwtimer_stop(timer); list_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 = list_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); list_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); } }}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?