From 48fff5eb58dcd8a0f4ef5791b85cc9333bdb1453 Mon Sep 17 00:00:00 2001 From: Jan Friesse Date: Thu, 18 Mar 2021 08:27:25 +0100 Subject: [PATCH] Implement heap based timer list (#439) * tlist: Add heap based implementation of timer list Previous timer was sorted list implementation of priority queue and very slow when number of timers increased. This is mostly not a problem because usually only few timers are used. But for application where bigger number of timers are needed it may become problem. Solution is to use binary heap based priority queue which is much faster. API is unchanged, just timerlist_destroy is added which should be called to free heap array. This function also destroys mutex (omitted when mutex was added). * tests: Fix check loop mt test test_th was accesed both by main thread and loop_timer thread resulting in failure. Fix is to access test_tht in loop_timer thread. Speed test is adding only 10000 items so it is reasonable fast even with sorted linked list implementation. Signed-off-by: Jan Friesse --- include/tlist.h | 338 ++++++++++++++++++++++++++++++++++++++----- lib/loop_timerlist.c | 10 +- tests/Makefile.am | 6 +- tests/check_loop.c | 2 +- tests/check_tlist.c | 294 +++++++++++++++++++++++++++++++++++++ 5 files changed, 606 insertions(+), 44 deletions(-) create mode 100644 tests/check_tlist.c diff --git a/include/tlist.h b/include/tlist.h index 62adb635b..6735c9816 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 children) -> 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; + + new_heap_entries = realloc(timerlist->heap_entries, + new_size * sizeof(timerlist->heap_entries[0])); + if (new_heap_entries == NULL) { + res = -errno; - if (timer_from_list->expire_time > timer->expire_time) { - qb_list_add_tail(&timer->list, timer_list); - found = QB_TRUE; - break; /* for timer iteration */ + 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; } @@ -113,15 +347,22 @@ static inline int32_t timerlist_add_duration(struct timerlist *timerlist, return (0); } -static inline void timerlist_del(struct timerlist *timerlist, +static inline int32_t timerlist_del(struct timerlist *timerlist, timer_handle _timer_handle) { struct timerlist_timer *timer = (struct timerlist_timer *)_timer_handle; + if (pthread_mutex_lock(&timerlist->list_mutex)) { + return -errno; + } + 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); + + pthread_mutex_unlock(&timerlist->list_mutex); + return 0; } static inline uint64_t timerlist_expire_time(struct timerlist @@ -140,8 +381,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, @@ -161,15 +402,28 @@ static inline uint64_t timerlist_msec_duration_to_expire(struct timerlist *timer volatile uint64_t current_time; volatile uint64_t msec_duration_to_expire; + /* + * There is really no reasonable value to return when mutex lock fails + */ + if (pthread_mutex_lock(&timerlist->list_mutex)) { + return (-1); + } + /* * empty list, no expire */ - if (qb_list_empty(&timerlist->timer_head)) { + if (timerlist->size == 0) { + pthread_mutex_unlock(&timerlist->list_mutex); + 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); + + /* + * Mutex is no longer needed + */ + pthread_mutex_unlock(&timerlist->list_mutex); if (timer_from_list->is_absolute_timer) { current_time = qb_util_nano_from_epoch_get(); @@ -193,11 +447,9 @@ static inline uint64_t timerlist_msec_duration_to_expire(struct timerlist *timer /* * Expires any timers that should be expired */ -static inline void timerlist_expire(struct timerlist *timerlist) +static inline int32_t 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,26 +457,32 @@ 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) { + if (pthread_mutex_lock(&timerlist->list_mutex)) { + return -errno; + } - 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 */ } } + + pthread_mutex_unlock(&timerlist->list_mutex); + + return (0); } #endif /* QB_TLIST_H_DEFINED */ diff --git a/lib/loop_timerlist.c b/lib/loop_timerlist.c index 9f14deda0..59cb1a138 100644 --- a/lib/loop_timerlist.c +++ b/lib/loop_timerlist.c @@ -76,7 +76,9 @@ expire_the_timers(struct qb_loop_source *s, int32_t ms_timeout) { struct qb_timer_source *ts = (struct qb_timer_source *)s; expired_timers = 0; - timerlist_expire(&ts->timerlist); + if (timerlist_expire(&ts->timerlist) != 0) { + qb_util_log(LOG_ERR, "timerlist_expire failed"); + } return expired_timers; } @@ -115,6 +117,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); } @@ -263,7 +267,9 @@ qb_loop_timer_del(struct qb_loop * lp, qb_loop_timer_handle th) } if (t->timerlist_handle) { - timerlist_del(&s->timerlist, t->timerlist_handle); + if (timerlist_del(&s->timerlist, t->timerlist_handle) != 0) { + qb_util_log(LOG_ERR, "Could not delete timer from timerlist"); + } } t->state = QB_POLL_ENTRY_EMPTY; return 0; diff --git a/tests/Makefile.am b/tests/Makefile.am index 8ee6b75df..7fc40b3df 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -125,7 +125,7 @@ resources.log: rb.log log.log ipc.log check_LTLIBRARIES = check_PROGRAMS = array.test ipc.test list.test log.test loop.test \ - map.test rb.test util.test \ + map.test rb.test util.test tlist.test \ crash_test_dummy file_change_bytes dist_check_SCRIPTS = start.test resources.test blackbox-segfault.sh @@ -162,6 +162,10 @@ loop_test_SOURCES = check_loop.c loop_test_CFLAGS = @CHECK_CFLAGS@ loop_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ +tlist_test_SOURCES = check_tlist.c +tlist_test_CFLAGS = @CHECK_CFLAGS@ +tlist_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ + ipc_test_SOURCES = check_ipc.c ipc_test_CFLAGS = @CHECK_CFLAGS@ ipc_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ diff --git a/tests/check_loop.c b/tests/check_loop.c index 9c696e947..ff54d786f 100644 --- a/tests/check_loop.c +++ b/tests/check_loop.c @@ -448,7 +448,7 @@ static void *loop_timer_thread(void *arg) res = qb_loop_timer_add(l, QB_LOOP_LOW, 5*QB_TIME_NS_IN_MSEC, l, one_shot_tmo, &test_tht); ck_assert_int_eq(res, 0); - res = qb_loop_timer_is_running(l, test_th); + res = qb_loop_timer_is_running(l, test_tht); ck_assert_int_eq(res, QB_TRUE); sleep(5); diff --git a/tests/check_tlist.c b/tests/check_tlist.c new file mode 100644 index 000000000..1fecec01a --- /dev/null +++ b/tests/check_tlist.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2021 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse + * + * This file is part of libqb. + * + * libqb 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 2.1 of the License, or + * (at your option) any later version. + * + * libqb 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libqb. If not, see . + */ + +#include "os_base.h" + +#include "check_common.h" + +#include "tlist.h" + +#include + +#include +#include +#include + +#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 HEAP_TEST_NO_ITEMS 20 +/* + * Valid heap checking is slow + */ +#define HEAP_SPEED_TEST_NO_ITEMS 1000 + +static int timer_list_fn1_called = 0; + +static void +timer_list_fn1(void *data) +{ + + ck_assert(data == &timer_list_fn1_called); + + timer_list_fn1_called++; +} + +static void +sleep_ns(long long int ns) +{ + + (void)poll(NULL, 0, (ns / QB_TIME_NS_IN_MSEC)); +} + +START_TEST(test_check_basic) +{ + struct timerlist tlist; + timer_handle thandle; + int res; + uint64_t u64; + + timerlist_init(&tlist); + + /* + * Check adding short duration and calling callback + */ + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, SHORT_TIMEOUT / 2, &thandle); + ck_assert_int_eq(res, 0); + + sleep_ns(SHORT_TIMEOUT); + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 == 0); + + timer_list_fn1_called = 0; + timerlist_expire(&tlist); + ck_assert_int_eq(timer_list_fn1_called, 1); + + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 == -1); + + /* + * Check callback is not called (long timeout) + */ + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, LONG_TIMEOUT / 2, &thandle); + ck_assert_int_eq(res, 0); + + sleep_ns(SHORT_TIMEOUT); + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 > 0); + + timer_list_fn1_called = 0; + timerlist_expire(&tlist); + ck_assert_int_eq(timer_list_fn1_called, 0); + + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 > 0); + + /* + * Delete timer + */ + timerlist_del(&tlist, thandle); + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 == -1); + + timerlist_destroy(&tlist); +} +END_TEST + +START_TEST(test_check_speed) +{ + struct timerlist tlist; + timer_handle thandle[SPEED_TEST_NO_ITEMS]; + int res; + uint64_t u64; + int i; + + timerlist_init(&tlist); + + /* + * Check adding a lot of short duration and deleting + */ + for (i = 0; i < SPEED_TEST_NO_ITEMS; i++) { + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, + SHORT_TIMEOUT / 2, &thandle[i]); + ck_assert_int_eq(res, 0); + } + + for (i = 0; i < SPEED_TEST_NO_ITEMS; i++) { + timerlist_del(&tlist, thandle[i]); + } + + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 == -1); + + /* + * Check adding a lot of short duration and calling callback + */ + for (i = 0; i < SPEED_TEST_NO_ITEMS; i++) { + res = timerlist_add_duration(&tlist, timer_list_fn1, &timer_list_fn1_called, + SHORT_TIMEOUT / 2, &thandle[i]); + ck_assert_int_eq(res, 0); + } + + u64 = timerlist_msec_duration_to_expire(&tlist); + ck_assert(u64 != -1); + + sleep_ns(SHORT_TIMEOUT); + + timer_list_fn1_called = 0; + timerlist_expire(&tlist); + ck_assert_int_eq(timer_list_fn1_called, SPEED_TEST_NO_ITEMS); + + 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 + +static Suite *tlist_suite(void) +{ + TCase *tc; + Suite *s = suite_create("tlist"); + + 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; +} + +int32_t main(void) +{ + int32_t number_failed; + + Suite *s = tlist_suite(); + SRunner *sr = srunner_create(s); + + qb_log_init("check", LOG_USER, LOG_EMERG); + atexit(qb_log_fini); + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); + qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, + QB_LOG_FILTER_FILE, "*", LOG_INFO); + qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); + + srunner_run_all(sr, CK_VERBOSE); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}