diff --git a/include/tlist.h b/include/tlist.h index 62adb635b..2c3b3ff71 100644 --- a/include/tlist.h +++ b/include/tlist.h @@ -1,7 +1,8 @@ /* - * Copyright (c) 2006-2007, 2009 Red Hat, Inc. + * Copyright (c) 2006-2007, 2009-2021 Red Hat, Inc. * - * Author: Steven Dake + * Author: Jan Friesse + * Steven Dake * * This file is part of libqb. * @@ -35,52 +36,284 @@ typedef void *timer_handle; static int64_t timerlist_hertz; struct timerlist { - struct qb_list_head timer_head; + struct timerlist_timer **heap_entries; + size_t allocated; + size_t size; pthread_mutex_t list_mutex; }; struct timerlist_timer { - struct qb_list_head list; uint64_t expire_time; int32_t is_absolute_timer; void (*timer_fn) (void *data); void *data; timer_handle handle_addr; + size_t heap_pos; }; +/* + * Heap helper functions + */ +static inline size_t +timerlist_heap_index_left(size_t index) +{ + + return (2 * index + 1); +} + +static inline size_t +timerlist_heap_index_right(size_t index) +{ + + return (2 * index + 2); +} + +static inline size_t +timerlist_heap_index_parent(size_t index) +{ + + return ((index - 1) / 2); +} + +static inline void +timerlist_heap_entry_set(struct timerlist *timerlist, size_t item_pos, struct timerlist_timer *timer) +{ + + assert(item_pos < timerlist->size); + + timerlist->heap_entries[item_pos] = timer; + timerlist->heap_entries[item_pos]->heap_pos = item_pos; +} + +static inline struct timerlist_timer * +timerlist_heap_entry_get(struct timerlist *timerlist, size_t item_pos) +{ + + assert(item_pos < timerlist->size); + + return (timerlist->heap_entries[item_pos]); +} + +static inline int +timerlist_entry_cmp(const struct timerlist_timer *t1, const struct timerlist_timer *t2) +{ + + if (t1->expire_time == t2->expire_time) { + return (0); + } else if (t1->expire_time < t2->expire_time) { + return (-1); + } else { + return (1); + } +} + +static inline void +timerlist_heap_sift_up(struct timerlist *timerlist, size_t item_pos) +{ + size_t parent_pos; + struct timerlist_timer *parent_timer; + struct timerlist_timer *timer; + + timer = timerlist_heap_entry_get(timerlist, item_pos); + + parent_pos = timerlist_heap_index_parent(item_pos); + + while (item_pos > 0 && + (parent_timer = timerlist_heap_entry_get(timerlist, parent_pos), + timerlist_entry_cmp(parent_timer, timer) > 0)) { + /* + * Swap item and parent + */ + timerlist_heap_entry_set(timerlist, parent_pos, timer); + timerlist_heap_entry_set(timerlist, item_pos, parent_timer); + + item_pos = parent_pos; + parent_pos = timerlist_heap_index_parent(item_pos); + } +} + +static inline void +timerlist_heap_sift_down(struct timerlist *timerlist, size_t item_pos) +{ + int cont; + size_t left_pos, right_pos, smallest_pos; + struct timerlist_timer *left_entry; + struct timerlist_timer *right_entry; + struct timerlist_timer *smallest_entry; + struct timerlist_timer *tmp_entry; + + cont = 1; + + while (cont) { + smallest_pos = item_pos; + left_pos = timerlist_heap_index_left(item_pos); + right_pos = timerlist_heap_index_right(item_pos); + + smallest_entry = timerlist_heap_entry_get(timerlist, smallest_pos); + + if (left_pos < timerlist->size && + (left_entry = timerlist_heap_entry_get(timerlist, left_pos), + timerlist_entry_cmp(left_entry, smallest_entry) < 0)) { + smallest_entry = left_entry; + smallest_pos = left_pos; + } + + if (right_pos < timerlist->size && + (right_entry = timerlist_heap_entry_get(timerlist, right_pos), + timerlist_entry_cmp(right_entry, smallest_entry) < 0)) { + smallest_entry = right_entry; + smallest_pos = right_pos; + } + + if (smallest_pos == item_pos) { + /* + * Item is smallest (or has no childs) -> heap property is restored + */ + cont = 0; + } else { + /* + * Swap item with smallest child + */ + tmp_entry = timerlist_heap_entry_get(timerlist, item_pos); + timerlist_heap_entry_set(timerlist, item_pos, smallest_entry); + timerlist_heap_entry_set(timerlist, smallest_pos, tmp_entry); + + item_pos = smallest_pos; + } + } +} + +static inline void +timerlist_heap_delete(struct timerlist *timerlist, struct timerlist_timer *entry) +{ + size_t entry_pos; + struct timerlist_timer *replacement_entry; + int cmp_entries; + + entry_pos = entry->heap_pos; + entry->heap_pos = (~(size_t)0); + + /* + * Swap element with last element + */ + replacement_entry = timerlist_heap_entry_get(timerlist, timerlist->size - 1); + timerlist_heap_entry_set(timerlist, entry_pos, replacement_entry); + + /* + * And "remove" last element (= entry) + */ + timerlist->size--; + + /* + * Up (or down) heapify based on replacement item size + */ + cmp_entries = timerlist_entry_cmp(replacement_entry, entry); + + if (cmp_entries < 0) { + timerlist_heap_sift_up(timerlist, entry_pos); + } else if (cmp_entries > 0) { + timerlist_heap_sift_down(timerlist, entry_pos); + } +} + +/* + * Check if heap is valid. + * - Shape property is always fullfiled because of storage in array + * - Check heap property + */ +static inline int +timerlist_debug_is_valid_heap(struct timerlist *timerlist) +{ + size_t i; + size_t left_pos, right_pos; + struct timerlist_timer *left_entry; + struct timerlist_timer *right_entry; + struct timerlist_timer *cur_entry; + + for (i = 0; i < timerlist->size; i++) { + cur_entry = timerlist_heap_entry_get(timerlist, i); + + left_pos = timerlist_heap_index_left(i); + right_pos = timerlist_heap_index_right(i); + + if (left_pos < timerlist->size && + (left_entry = timerlist_heap_entry_get(timerlist, left_pos), + timerlist_entry_cmp(left_entry, cur_entry) < 0)) { + return (0); + } + + if (right_pos < timerlist->size && + (right_entry = timerlist_heap_entry_get(timerlist, right_pos), + timerlist_entry_cmp(right_entry, cur_entry) < 0)) { + return (0); + } + } + + return (1); +} + +/* + * Main functions implementation + */ static inline void timerlist_init(struct timerlist *timerlist) { - qb_list_init(&timerlist->timer_head); + + memset(timerlist, 0, sizeof(*timerlist)); + + timerlist->heap_entries = NULL; pthread_mutex_init(&timerlist->list_mutex, NULL); timerlist_hertz = qb_util_nano_monotonic_hz(); } +static inline void timerlist_destroy(struct timerlist *timerlist) +{ + size_t zi; + + pthread_mutex_destroy(&timerlist->list_mutex); + + for (zi = 0; zi < timerlist->size; zi++) { + free(timerlist->heap_entries[zi]); + } + free(timerlist->heap_entries); +} + static inline int32_t timerlist_add(struct timerlist *timerlist, struct timerlist_timer *timer) { - struct qb_list_head *timer_list = 0; - struct timerlist_timer *timer_from_list; - int32_t found = QB_FALSE; + size_t new_size; + struct timerlist_timer **new_heap_entries; + int32_t res = 0; if (pthread_mutex_lock(&timerlist->list_mutex)) { return -errno; } - qb_list_for_each(timer_list, &timerlist->timer_head) { - timer_from_list = qb_list_entry(timer_list, - struct timerlist_timer, list); + /* + * Check that heap array is large enough + */ + if (timerlist->size + 1 > timerlist->allocated) { + new_size = (timerlist->allocated + 1) * 2; - if (timer_from_list->expire_time > timer->expire_time) { - qb_list_add_tail(&timer->list, timer_list); - found = QB_TRUE; - break; /* for timer iteration */ + new_heap_entries = realloc(timerlist->heap_entries, + new_size * sizeof(timerlist->heap_entries[0])); + if (new_heap_entries == NULL) { + res = -errno; + + goto cleanup; } + + timerlist->allocated = new_size; + timerlist->heap_entries = new_heap_entries; } - if (found == QB_FALSE) { - qb_list_add_tail(&timer->list, &timerlist->timer_head); - } + + timerlist->size++; + + timerlist_heap_entry_set(timerlist, timerlist->size - 1, timer); + timerlist_heap_sift_up(timerlist, timerlist->size - 1); + +cleanup: pthread_mutex_unlock(&timerlist->list_mutex); - return 0; + return res; } static inline int32_t timerlist_add_duration(struct timerlist *timerlist, @@ -94,7 +327,8 @@ static inline int32_t timerlist_add_duration(struct timerlist *timerlist, timer = (struct timerlist_timer *)malloc(sizeof(struct timerlist_timer)); - if (timer == 0) { + + if (timer == NULL) { return -ENOMEM; } @@ -119,8 +353,8 @@ static inline void timerlist_del(struct timerlist *timerlist, struct timerlist_timer *timer = (struct timerlist_timer *)_timer_handle; memset(timer->handle_addr, 0, sizeof(struct timerlist_timer *)); - qb_list_del(&timer->list); - qb_list_init(&timer->list); + + timerlist_heap_delete(timerlist, timer); free(timer); } @@ -140,8 +374,8 @@ static inline void timerlist_pre_dispatch(struct timerlist *timerlist, struct timerlist_timer *timer = (struct timerlist_timer *)_timer_handle; memset(timer->handle_addr, 0, sizeof(struct timerlist_timer *)); - qb_list_del(&timer->list); - qb_list_init(&timer->list); + + timerlist_heap_delete(timerlist, timer); } static inline void timerlist_post_dispatch(struct timerlist *timerlist, @@ -164,12 +398,11 @@ static inline uint64_t timerlist_msec_duration_to_expire(struct timerlist *timer /* * empty list, no expire */ - if (qb_list_empty(&timerlist->timer_head)) { + if (timerlist->size == 0) { return (-1); } - timer_from_list = qb_list_first_entry(&timerlist->timer_head, - struct timerlist_timer, list); + timer_from_list = timerlist_heap_entry_get(timerlist, 0); if (timer_from_list->is_absolute_timer) { current_time = qb_util_nano_from_epoch_get(); @@ -195,9 +428,7 @@ static inline uint64_t timerlist_msec_duration_to_expire(struct timerlist *timer */ static inline void timerlist_expire(struct timerlist *timerlist) { - struct timerlist_timer *timer_from_list; - struct qb_list_head *pos; - struct qb_list_head *next; + struct timerlist_timer *timer; uint64_t current_time_from_epoch; uint64_t current_monotonic_time; uint64_t current_time; @@ -205,23 +436,21 @@ static inline void timerlist_expire(struct timerlist *timerlist) current_monotonic_time = qb_util_nano_current_get(); current_time_from_epoch = qb_util_nano_from_epoch_get(); - qb_list_for_each_safe(pos, next, &timerlist->timer_head) { - - timer_from_list = qb_list_entry(pos, - struct timerlist_timer, list); + while (timerlist->size > 0) { + timer = timerlist_heap_entry_get(timerlist, 0); current_time = - (timer_from_list-> + (timer-> is_absolute_timer ? current_time_from_epoch : current_monotonic_time); - if (timer_from_list->expire_time < current_time) { + if (timer->expire_time < current_time) { - timerlist_pre_dispatch(timerlist, timer_from_list); + timerlist_pre_dispatch(timerlist, timer); - timer_from_list->timer_fn(timer_from_list->data); + timer->timer_fn(timer->data); - timerlist_post_dispatch(timerlist, timer_from_list); + timerlist_post_dispatch(timerlist, timer); } else { break; /* for timer iteration */ } diff --git a/lib/loop_timerlist.c b/lib/loop_timerlist.c index 9f14deda0..f74c485a5 100644 --- a/lib/loop_timerlist.c +++ b/lib/loop_timerlist.c @@ -115,6 +115,8 @@ qb_loop_timer_destroy(struct qb_loop *l) { struct qb_timer_source *my_src = (struct qb_timer_source *)l->timer_source; + + timerlist_destroy(&my_src->timerlist); qb_array_free(my_src->timers); free(l->timer_source); } diff --git a/tests/check_tlist.c b/tests/check_tlist.c index 0a4ae2e00..1fecec01a 100644 --- a/tests/check_tlist.c +++ b/tests/check_tlist.c @@ -33,10 +33,16 @@ #include #include -#define SHORT_TIMEOUT (100 * QB_TIME_NS_IN_MSEC) -#define LONG_TIMEOUT (60 * QB_TIME_NS_IN_SEC) +#define SHORT_TIMEOUT (100 * QB_TIME_NS_IN_MSEC) +#define LONG_TIMEOUT (60 * QB_TIME_NS_IN_SEC) -#define SPEED_TEST_NO_ITEMS 10000 +#define SPEED_TEST_NO_ITEMS 10000 + +#define HEAP_TEST_NO_ITEMS 20 +/* + * Valid heap checking is slow + */ +#define HEAP_SPEED_TEST_NO_ITEMS 1000 static int timer_list_fn1_called = 0; @@ -105,6 +111,8 @@ START_TEST(test_check_basic) timerlist_del(&tlist, thandle); u64 = timerlist_msec_duration_to_expire(&tlist); ck_assert(u64 == -1); + + timerlist_destroy(&tlist); } END_TEST @@ -154,6 +162,102 @@ START_TEST(test_check_speed) u64 = timerlist_msec_duration_to_expire(&tlist); ck_assert(u64 == -1); + + timerlist_destroy(&tlist); +} +END_TEST + +START_TEST(test_check_heap) +{ + struct timerlist tlist; + int i; + timer_handle tlist_entry[HEAP_TEST_NO_ITEMS]; + timer_handle tlist_speed_entry[HEAP_SPEED_TEST_NO_ITEMS]; + int res; + + timerlist_init(&tlist); + + /* + * Empty tlist + */ + ck_assert(timerlist_msec_duration_to_expire(&tlist) == -1); + + /* + * Add items in standard and reverse order + */ + for (i = 0; i < HEAP_TEST_NO_ITEMS / 2; i++) { + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, + LONG_TIMEOUT * ((HEAP_TEST_NO_ITEMS - i) + 1), &tlist_entry[i * 2]); + ck_assert_int_eq(res, 0); + + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, + LONG_TIMEOUT * (i + 1), &tlist_entry[i * 2 + 1]); + ck_assert_int_eq(res, 0); + + ck_assert(timerlist_debug_is_valid_heap(&tlist)); + } + + /* + * Remove items + */ + for (i = 0; i < HEAP_TEST_NO_ITEMS; i++) { + timerlist_del(&tlist, tlist_entry[i]); + + ck_assert(timerlist_debug_is_valid_heap(&tlist)); + } + + ck_assert(timerlist_msec_duration_to_expire(&tlist) == -1); + + /* + * Add items again in increasing order + */ + for (i = 0; i < HEAP_TEST_NO_ITEMS; i++) { + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, + LONG_TIMEOUT * (i + 1), &tlist_entry[i]); + ck_assert_int_eq(res, 0); + + ck_assert(timerlist_debug_is_valid_heap(&tlist)); + } + + + /* + * Try delete every third item and test if heap property is kept + */ + i = 0; + while (tlist.size > 0) { + i = (i + 3) % HEAP_TEST_NO_ITEMS; + + while (tlist_entry[i] == NULL) { + i = (i + 1) % HEAP_TEST_NO_ITEMS; + } + + timerlist_del(&tlist, tlist_entry[i]); + tlist_entry[i] = NULL; + ck_assert(timerlist_debug_is_valid_heap(&tlist)); + } + + ck_assert(timerlist_msec_duration_to_expire(&tlist) == -1); + + /* + * Speed test + */ + for (i = 0; i < HEAP_SPEED_TEST_NO_ITEMS; i++) { + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, + SHORT_TIMEOUT / 2, &tlist_speed_entry[i]); + ck_assert_int_eq(res, 0); + + ck_assert(timerlist_debug_is_valid_heap(&tlist)); + } + + for (i = 0; i < HEAP_SPEED_TEST_NO_ITEMS; i++) { + timerlist_del(&tlist, tlist_speed_entry[i]); + ck_assert(timerlist_debug_is_valid_heap(&tlist)); + } + + /* + * Free list + */ + timerlist_destroy(&tlist); } END_TEST @@ -164,6 +268,7 @@ static Suite *tlist_suite(void) add_tcase(s, tc, test_check_basic); add_tcase(s, tc, test_check_speed, 30); + add_tcase(s, tc, test_check_heap, 30); return s; }