diff --git a/include/tlist.h b/include/tlist.h index 62adb63..6735c98 100644 --- a/include/tlist.h +++ b/include/tlist.h @@ -1,230 +1,488 @@ /* - * 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. * * 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 . */ #ifndef QB_TLIST_H_DEFINED #define QB_TLIST_H_DEFINED #include "os_base.h" #include #include #include #ifndef TIMER_HANDLE typedef void *timer_handle; #define TIMER_HANDLE #endif 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, void (*timer_fn) (void *data), void *data, uint64_t nano_duration, timer_handle * handle) { int res; struct timerlist_timer *timer; timer = (struct timerlist_timer *)malloc(sizeof(struct timerlist_timer)); - if (timer == 0) { + + if (timer == NULL) { return -ENOMEM; } timer->expire_time = qb_util_nano_current_get() + nano_duration; timer->is_absolute_timer = QB_FALSE; timer->data = data; timer->timer_fn = timer_fn; timer->handle_addr = handle; res = timerlist_add(timerlist, timer); if (res) { free(timer); return res; } *handle = timer; 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 *timerlist, timer_handle _timer_handle) { struct timerlist_timer *timer = (struct timerlist_timer *)_timer_handle; return (timer->expire_time); } static inline void timerlist_pre_dispatch(struct timerlist *timerlist, timer_handle _timer_handle) { 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, timer_handle _timer_handle) { struct timerlist_timer *timer = (struct timerlist_timer *)_timer_handle; free(timer); } /* * returns the number of msec until the next timer will expire for use with poll */ static inline uint64_t timerlist_msec_duration_to_expire(struct timerlist *timerlist) { struct timerlist_timer *timer_from_list; 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(); } else { current_time = qb_util_nano_current_get(); } /* * timer at head of list is expired, zero msecs required */ if (timer_from_list->expire_time < current_time) { return (0); } msec_duration_to_expire = ((timer_from_list->expire_time - current_time) / QB_TIME_NS_IN_MSEC) + (1000 / timerlist_hertz); return (msec_duration_to_expire); } /* * 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; 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 9f14ded..59cb1a1 100644 --- a/lib/loop_timerlist.c +++ b/lib/loop_timerlist.c @@ -1,351 +1,357 @@ /* * Copyright (C) 2010 Red Hat, Inc. * * Author: Angus Salkeld * * 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 #include #include #include #include #include "loop_int.h" #include "util_int.h" #include "tlist.h" struct qb_loop_timer { struct qb_loop_item item; qb_loop_timer_dispatch_fn dispatch_fn; enum qb_loop_priority p; timer_handle timerlist_handle; enum qb_poll_entry_state state; int32_t check; uint32_t install_pos; }; struct qb_timer_source { struct qb_loop_source s; struct timerlist timerlist; qb_array_t *timers; size_t timer_entry_count; pthread_mutex_t lock; }; static void timer_dispatch(struct qb_loop_item *item, enum qb_loop_priority p) { struct qb_loop_timer *timer = (struct qb_loop_timer *)item; assert(timer->state == QB_POLL_ENTRY_JOBLIST); timer->check = 0; timer->dispatch_fn(timer->item.user_data); timer->state = QB_POLL_ENTRY_EMPTY; } static int32_t expired_timers; static void make_job_from_tmo(void *data) { struct qb_loop_timer *t = (struct qb_loop_timer *)data; struct qb_loop *l = t->item.source->l; assert(t->state == QB_POLL_ENTRY_ACTIVE); qb_loop_level_item_add(&l->level[t->p], &t->item); t->state = QB_POLL_ENTRY_JOBLIST; expired_timers++; } static int32_t 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; } int32_t qb_loop_timer_msec_duration_to_expire(struct qb_loop_source * timer_source) { struct qb_timer_source *my_src = (struct qb_timer_source *)timer_source; uint64_t left = timerlist_msec_duration_to_expire(&my_src->timerlist); if (left != -1 && left > 0xFFFFFFFF) { left = 0xFFFFFFFE; } return left; } struct qb_loop_source * qb_loop_timer_create(struct qb_loop *l) { struct qb_timer_source *my_src = malloc(sizeof(struct qb_timer_source)); if (my_src == NULL) { return NULL; } my_src->s.l = l; my_src->s.dispatch_and_take_back = timer_dispatch; my_src->s.poll = expire_the_timers; timerlist_init(&my_src->timerlist); my_src->timers = qb_array_create_2(16, sizeof(struct qb_loop_timer), 16); my_src->timer_entry_count = 0; pthread_mutex_init(&my_src->lock, NULL); return (struct qb_loop_source *)my_src; } void 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); } static int32_t _timer_from_handle_(struct qb_timer_source *s, qb_loop_timer_handle handle_in, struct qb_loop_timer **timer_pt) { int32_t rc; int32_t check; uint32_t install_pos; struct qb_loop_timer *timer; if (handle_in == 0) { return -EINVAL; } check = handle_in >> 32; install_pos = handle_in & UINT32_MAX; rc = qb_array_index(s->timers, install_pos, (void **)&timer); if (rc != 0) { return rc; } if (timer->check != check) { return -EINVAL; } *timer_pt = timer; return 0; } static int32_t _get_empty_array_position_(struct qb_timer_source *s) { int32_t install_pos; int32_t res = 0; struct qb_loop_timer *timer; for (install_pos = 0; install_pos < s->timer_entry_count; install_pos++) { assert(qb_array_index(s->timers, install_pos, (void **)&timer) == 0); if (timer->state == QB_POLL_ENTRY_EMPTY) { return install_pos; } } res = qb_array_grow(s->timers, s->timer_entry_count + 1); if (res != 0) { return res; } s->timer_entry_count++; install_pos = s->timer_entry_count - 1; return install_pos; } int32_t qb_loop_timer_add(struct qb_loop * lp, enum qb_loop_priority p, uint64_t nsec_duration, void *data, qb_loop_timer_dispatch_fn timer_fn, qb_loop_timer_handle * timer_handle_out) { struct qb_loop_timer *t; struct qb_timer_source *my_src; int32_t i; struct qb_loop *l = lp; if (l == NULL) { l = qb_loop_default_get(); } if (l == NULL || timer_fn == NULL) { return -EINVAL; } my_src = (struct qb_timer_source *)l->timer_source; if (pthread_mutex_lock(&my_src->lock)) { return -errno; } i = _get_empty_array_position_(my_src); assert(qb_array_index(my_src->timers, i, (void **)&t) >= 0); t->state = QB_POLL_ENTRY_ACTIVE; t->install_pos = i; t->item.user_data = data; t->item.source = (struct qb_loop_source *)my_src; t->dispatch_fn = timer_fn; t->p = p; qb_list_init(&t->item.list); /* Unlock here to stop anyone else changing the state while we're initializing */ pthread_mutex_unlock(&my_src->lock); /* * Make sure just positive integers are used for the integrity(?) * checks within 2^32 address space, if we miss 200 times in a row * (just 0 is concerned per specification of random), the PRNG may be * broken -> the value is unspecified, subject of previous assignment. */ for (i = 0; i < 200; i++) { t->check = random(); if (t->check > 0) { break; /* covers also t->check == UINT32_MAX */ } } if (timer_handle_out) { *timer_handle_out = (((uint64_t) (t->check)) << 32) | t->install_pos; } return timerlist_add_duration(&my_src->timerlist, make_job_from_tmo, t, nsec_duration, &t->timerlist_handle); } int32_t qb_loop_timer_del(struct qb_loop * lp, qb_loop_timer_handle th) { struct qb_timer_source *s; struct qb_loop_timer *t; int32_t res; struct qb_loop *l = lp; if (l == NULL) { l = qb_loop_default_get(); } s = (struct qb_timer_source *)l->timer_source; res = _timer_from_handle_(s, th, &t); if (res != 0) { return res; } if (t->state == QB_POLL_ENTRY_DELETED) { qb_util_log(LOG_WARNING, "timer already deleted"); return 0; } if (t->state != QB_POLL_ENTRY_ACTIVE && t->state != QB_POLL_ENTRY_JOBLIST) { return -EINVAL; } if (t->state == QB_POLL_ENTRY_JOBLIST) { qb_loop_level_item_del(&l->level[t->p], &t->item); } 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; } uint64_t qb_loop_timer_expire_time_get(struct qb_loop * lp, qb_loop_timer_handle th) { struct qb_timer_source *s; struct qb_loop_timer *t; int32_t res; struct qb_loop *l = lp; if (l == NULL) { l = qb_loop_default_get(); } s = (struct qb_timer_source *)l->timer_source; res = _timer_from_handle_(s, th, &t); if (res != 0) { return 0; } if (t->state != QB_POLL_ENTRY_ACTIVE) { return 0; } return timerlist_expire_time(&s->timerlist, t->timerlist_handle); } uint64_t qb_loop_timer_expire_time_remaining(struct qb_loop * lp, qb_loop_timer_handle th) { uint64_t current_ns; /* NOTE: while it does not appear that absolute timers are used anywhere, * we may as well respect this pattern in case that changes. * Unfortunately, that means we do need to repeat timer fetch code from qb_loop_timer_expire_time_get * rather than just a simple call to qb_loop_timer_expire_time_get and qb_util_nano_current_get. */ struct qb_timer_source *s; struct qb_loop_timer *t; int32_t res; struct qb_loop *l = lp; if (l == NULL) { l = qb_loop_default_get(); } s = (struct qb_timer_source *)l->timer_source; res = _timer_from_handle_(s, th, &t); if (res != 0) { return 0; } struct timerlist_timer *timer = (struct timerlist_timer *)t->timerlist_handle; if (timer->is_absolute_timer) { current_ns = qb_util_nano_from_epoch_get(); } else { current_ns = qb_util_nano_current_get(); } uint64_t timer_ns = timerlist_expire_time(&s->timerlist, t->timerlist_handle); /* since time estimation is racy by nature, I'll try to check the state late, * and try to understand that no matter what, the timer might have expired in the mean time */ if (t->state != QB_POLL_ENTRY_ACTIVE) { return 0; } if (timer_ns < current_ns) { return 0; // respect the "expired" contract } return timer_ns - current_ns; } int32_t qb_loop_timer_is_running(qb_loop_t *l, qb_loop_timer_handle th) { return (qb_loop_timer_expire_time_get(l, th) > 0); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 8ee6b75..7fc40b3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,203 +1,207 @@ # Copyright (c) 2010 Red Hat, Inc. # # Authors: Angus Salkeld # # 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 . # MAINTAINERCLEANFILES = Makefile.in EXTRA_DIST = CLEANFILES = export SOCKETDIR AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include noinst_PROGRAMS = bmc bmcpt bms rbreader rbwriter \ bench-log format_compare_speed loop print_ver \ $(check_PROGRAMS) noinst_HEADERS = check_common.h format_compare_speed_SOURCES = format_compare_speed.c $(top_builddir)/include/qb/qbutil.h format_compare_speed_LDADD = $(top_builddir)/lib/libqb.la bmc_SOURCES = bmc.c bmc_LDADD = $(top_builddir)/lib/libqb.la bmcpt_SOURCES = bmcpt.c bmcpt_CFLAGS = $(PTHREAD_CFLAGS) bmcpt_LDADD = $(PTHREAD_LIBS) $(top_builddir)/lib/libqb.la bms_SOURCES = bms.c bms_CFLAGS = $(GLIB_CFLAGS) bms_LDADD = $(top_builddir)/lib/libqb.la $(GLIB_LIBS) rbwriter_SOURCES = rbwriter.c rbwriter_LDADD = $(top_builddir)/lib/libqb.la rbreader_SOURCES = rbreader.c rbreader_LDADD = $(top_builddir)/lib/libqb.la loop_SOURCES = loop.c loop_LDADD = $(top_builddir)/lib/libqb.la inc_dir = $(top_srcdir)/include/qb public_headers = $(sort $(patsubst %.in,%,$(subst $(inc_dir)/,,$(shell \ printf 'include $(inc_dir)/Makefile.am\n\n%%.var:\n\t@echo $$($$*)' \ | MAKEFLAGS= ${MAKE} --no-print-directory -f- inst_HEADERS.var \ || echo $(inc_dir)/qb*.h*)))) auto_c_files = $(patsubst %.h,auto_check_header_%.c,$(public_headers)) CLEANFILES += $(auto_c_files) # this works for both non/generated headers thanks to VPATH being # automatically set to $(top_srcdir)/tests and $(top_builddir) # being resolved to ".." by automake # ($(top_srcdir)/tests/../include/qb/%.h = $(top_srcdir)/include/qb/%.h) auto_check_header_%.c: $(top_builddir)/include/qb/%.h @name=$$(echo "$<" | sed "s|.*qb/qb||" | sed "s|\.h||") ;\ NAME=$$(echo $$name | tr [:lower:] [:upper:]) ;\ echo "#include " > $@_ ;\ echo "#ifndef QB_$${NAME}_H_DEFINED" >> $@_ ;\ echo "#error no header protector in file qb$$name.h" >> $@_ ;\ echo "#endif" >> $@_ ;\ echo "int main(void) {return 0;}" >> $@_ $(AM_V_GEN)mv $@_ $@ check: check-headers # rely on implicit automake rule to include right (local) includes .PHONY: check-headers check-headers: $(auto_c_files:.c=.o) $(auto_c_files:.c=.opp) # this is to check basic sanity of using libqb from C++ code, if possible %.opp: %.c if HAVE_GXX $(AM_V_GEN)$(CXX) $(AM_CPPFLAGS) -c $(CPPFLAGS) $(CXXFLAGS) -o $@ $< else @echo "C++ compatibility tests not run" endif CLEANFILES += ${auto_c_files:.c=.opp} distclean-local: rm -rf auto_*.c rm -rf .deps if HAVE_DICT_WORDS if HAVE_SLOW_TESTS EXTRA_DIST += make-log-test.sh CLEANFILES += auto_write_logs.c MAINTAINERCLEANFILES += auto_write_logs.c nodist_bench_log_SOURCES = auto_write_logs.c bench_log: auto_write_logs.c $(builddir)/auto_write_logs.c: make-log-test.sh @$(srcdir)/make-log-test.sh > $(builddir)/write_logs-tmp.c $(AM_V_GEN)mv $(builddir)/write_logs-tmp.c $(builddir)/auto_write_logs.c endif endif bench_log_SOURCES = bench-log.c bench_log_LDADD = $(top_builddir)/lib/libqb.la if HAVE_CHECK EXTRA_DIST += start.test resources.test EXTRA_DIST += blackbox-segfault.sh TESTS = start.test array.test map.test rb.test list.test log.test blackbox-segfault.sh loop.test ipc.test resources.test TESTS_ENVIRONMENT = export PATH=.:../tools:$$PATH; 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 if HAVE_SLOW_TESTS TESTS += util.test check_PROGRAMS += util.test endif if INSTALL_TESTS testsuitedir = $(TESTDIR) testsuite_PROGRAMS = $(check_PROGRAMS) testsuite_SCRIPTS = $(dist_check_SCRIPTS) test.conf endif file_change_bytes_SOURCES = file_change_bytes.c crash_test_dummy_SOURCES = crash_test_dummy.c crash_test_dummy_CFLAGS = @CHECK_CFLAGS@ crash_test_dummy_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ array_test_SOURCES = check_array.c array_test_CFLAGS = @CHECK_CFLAGS@ array_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ map_test_SOURCES = check_map.c map_test_CFLAGS = @CHECK_CFLAGS@ map_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ rb_test_SOURCES = check_rb.c rb_test_CFLAGS = @CHECK_CFLAGS@ rb_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ 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@ if HAVE_FAILURE_INJECTION ipc_test_LDADD += _failure_injection.la if HAVE_GLIB ipc_test_CFLAGS += $(GLIB_CFLAGS) ipc_test_LDADD += $(GLIB_LIBS) endif check_LTLIBRARIES += _failure_injection.la _failure_injection_la_SOURCES = _failure_injection.c _failure_injection.h _failure_injection_la_LDFLAGS = -module _failure_injection_la_LIBADD = $(dlopen_LIBS) endif check_LTLIBRARIES += _syslog_override.la _syslog_override_la_SOURCES = _syslog_override.c _syslog_override.h _syslog_override_la_LDFLAGS = -module log_test_SOURCES = check_log.c log_test_CFLAGS = @CHECK_CFLAGS@ log_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ log_test_LDADD += _syslog_override.la util_test_SOURCES = check_util.c util_test_CFLAGS = @CHECK_CFLAGS@ util_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ list_test_SOURCES = check_list.c list_test_CFLAGS = @CHECK_CFLAGS@ list_test_LDADD = $(top_builddir)/lib/libqb.la @CHECK_LIBS@ endif clean-local: rm -f *.log rm -f *.fdata diff --git a/tests/check_loop.c b/tests/check_loop.c index 9c696e9..ff54d78 100644 --- a/tests/check_loop.c +++ b/tests/check_loop.c @@ -1,833 +1,833 @@ /* * Copyright (c) 2010 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld * * 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 #include #include #include static int32_t job_1_run_count = 0; static int32_t job_2_run_count = 0; static int32_t job_3_run_count = 0; static int32_t job_order_1 = 1; static int32_t job_order_2 = 2; static int32_t job_order_3 = 3; static int32_t job_order_4 = 4; static int32_t job_order_5 = 5; static int32_t job_order_6 = 6; static int32_t job_order_7 = 7; static int32_t job_order_8 = 8; static int32_t job_order_9 = 9; static int32_t job_order_10 = 10; static int32_t job_order_11 = 11; static int32_t job_order_12 = 12; static int32_t job_order_13 = 13; static void job_1(void *data) { job_1_run_count++; } static void job_order_check(void *data) { int32_t * order = (int32_t *)data; job_1_run_count++; ck_assert_int_eq(job_1_run_count, *order); if (job_1_run_count == 1) { qb_loop_job_add(NULL, QB_LOOP_MED, &job_order_10, job_order_check); qb_loop_job_add(NULL, QB_LOOP_MED, &job_order_11, job_order_check); qb_loop_job_add(NULL, QB_LOOP_MED, &job_order_12, job_order_check); qb_loop_job_add(NULL, QB_LOOP_MED, &job_order_13, job_order_check); } else if (job_1_run_count >= 13) { qb_loop_stop(NULL); } } static void job_stop(void *data) { qb_loop_t *l = (qb_loop_t *)data; job_3_run_count++; qb_loop_stop(l); } static void job_2(void *data) { int32_t res; qb_loop_t *l = (qb_loop_t *)data; job_2_run_count++; res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_stop); ck_assert_int_eq(res, 0); } static void job_1_r(void *data) { int32_t res; qb_loop_t *l = (qb_loop_t *)data; job_1_run_count++; res = qb_loop_job_add(l, QB_LOOP_MED, data, job_2); ck_assert_int_eq(res, 0); } static void job_1_add_nuts(void *data) { int32_t res; qb_loop_t *l = (qb_loop_t *)data; job_1_run_count++; res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_LOW, data, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_LOW, data, job_1); ck_assert_int_eq(res, 0); if (job_1_run_count < 500) { res = qb_loop_job_add(l, QB_LOOP_LOW, data, job_1_add_nuts); ck_assert_int_eq(res, 0); } else { res = qb_loop_job_add(l, QB_LOOP_LOW, data, job_stop); ck_assert_int_eq(res, 0); } ck_assert_int_eq(res, 0); } START_TEST(test_loop_job_input) { int32_t res; qb_loop_t *l; res = qb_loop_job_add(NULL, QB_LOOP_LOW, NULL, job_2); ck_assert_int_eq(res, -EINVAL); l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_job_add(NULL, QB_LOOP_LOW, NULL, job_2); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, 89, NULL, job_2); ck_assert_int_eq(res, -EINVAL); res = qb_loop_job_add(l, QB_LOOP_LOW, NULL, NULL); ck_assert_int_eq(res, -EINVAL); qb_loop_destroy(l); } END_TEST START_TEST(test_loop_job_1) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_job_add(l, QB_LOOP_LOW, NULL, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_LOW, l, job_stop); ck_assert_int_eq(res, 0); qb_loop_run(l); ck_assert_int_eq(job_1_run_count, 1); qb_loop_destroy(l); } END_TEST START_TEST(test_loop_job_4) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_job_add(l, QB_LOOP_LOW, l, job_1_r); ck_assert_int_eq(res, 0); qb_loop_run(l); ck_assert_int_eq(job_1_run_count, 1); ck_assert_int_eq(job_2_run_count, 1); ck_assert_int_eq(job_3_run_count, 1); qb_loop_destroy(l); } END_TEST START_TEST(test_loop_job_nuts) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_job_add(l, QB_LOOP_LOW, l, job_1_add_nuts); ck_assert_int_eq(res, 0); qb_loop_run(l); ck_assert(job_1_run_count >= 500); qb_loop_destroy(l); } END_TEST START_TEST(test_loop_job_order) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); job_1_run_count = 0; res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_1, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_2, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_3, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_4, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_5, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_6, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_7, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_8, job_order_check); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, &job_order_9, job_order_check); ck_assert_int_eq(res, 0); qb_loop_run(l); qb_loop_destroy(l); } END_TEST static qb_util_stopwatch_t *rl_sw; #define RATE_LIMIT_RUNTIME_SEC 3 static void job_add_self(void *data) { int32_t res; uint64_t elapsed1; qb_loop_t *l = (qb_loop_t *)data; job_1_run_count++; qb_util_stopwatch_stop(rl_sw); elapsed1 = qb_util_stopwatch_us_elapsed_get(rl_sw); if (elapsed1 > (RATE_LIMIT_RUNTIME_SEC * QB_TIME_US_IN_SEC)) { /* run for 3 seconds */ qb_loop_stop(l); return; } res = qb_loop_job_add(l, QB_LOOP_MED, data, job_add_self); ck_assert_int_eq(res, 0); } START_TEST(test_job_rate_limit) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); rl_sw = qb_util_stopwatch_create(); ck_assert(rl_sw != NULL); qb_util_stopwatch_start(rl_sw); res = qb_loop_job_add(l, QB_LOOP_MED, l, job_add_self); ck_assert_int_eq(res, 0); qb_loop_run(l); /* * the test is to confirm that a single job does not run away * and cause cpu spin. We are going to say that a spin is more than * one job per 50ms if there is only one job pending in the loop. */ _ck_assert_int(job_1_run_count, <, (RATE_LIMIT_RUNTIME_SEC * (QB_TIME_MS_IN_SEC/50)) + 10); qb_loop_destroy(l); qb_util_stopwatch_free(rl_sw); } END_TEST static void job_stop_and_del_1(void *data) { int32_t res; qb_loop_t *l = (qb_loop_t *)data; job_3_run_count++; res = qb_loop_job_del(l, QB_LOOP_MED, l, job_1); ck_assert_int_eq(res, 0); qb_loop_stop(l); } START_TEST(test_job_add_del) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_job_add(l, QB_LOOP_MED, l, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_del(l, QB_LOOP_MED, l, job_1); ck_assert_int_eq(res, 0); job_1_run_count = 0; job_3_run_count = 0; res = qb_loop_job_add(l, QB_LOOP_MED, l, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, l, job_stop_and_del_1); ck_assert_int_eq(res, 0); qb_loop_run(l); ck_assert_int_eq(job_1_run_count, 0); ck_assert_int_eq(job_3_run_count, 1); qb_loop_destroy(l); } END_TEST static Suite *loop_job_suite(void) { TCase *tc; Suite *s = suite_create("loop_job"); add_tcase(s, tc, test_loop_job_input); add_tcase(s, tc, test_loop_job_1); add_tcase(s, tc, test_loop_job_4); add_tcase(s, tc, test_loop_job_nuts, 5); add_tcase(s, tc, test_job_rate_limit, 5); add_tcase(s, tc, test_job_add_del); add_tcase(s, tc, test_loop_job_order); return s; } /* * ----------------------------------------------------------------------- * Timers */ static qb_loop_timer_handle test_th; static qb_loop_timer_handle test_th2; static void check_time_left(void *data) { qb_loop_t *l = (qb_loop_t *)data; /* NOTE: We are checking the 'stop_loop' timer here, not our own */ uint64_t abs_time = qb_loop_timer_expire_time_get(l, test_th); uint64_t rel_time = qb_loop_timer_expire_time_remaining(l, test_th); ck_assert(abs_time > 0ULL); ck_assert(rel_time > 0ULL); ck_assert(abs_time > rel_time); ck_assert(rel_time <= 60*QB_TIME_NS_IN_MSEC); } START_TEST(test_loop_timer_input) { int32_t res; qb_loop_t *l; res = qb_loop_timer_add(NULL, QB_LOOP_LOW, 5*QB_TIME_NS_IN_MSEC, NULL, job_2, &test_th); ck_assert_int_eq(res, -EINVAL); l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_timer_add(NULL, QB_LOOP_LOW, 5*QB_TIME_NS_IN_MSEC, NULL, job_2, &test_th); ck_assert_int_eq(res, 0); res = qb_loop_timer_add(l, QB_LOOP_LOW, 5*QB_TIME_NS_IN_MSEC, l, NULL, &test_th); ck_assert_int_eq(res, -EINVAL); qb_loop_destroy(l); } END_TEST static void one_shot_tmo(void * data) { static int32_t been_here = QB_FALSE; ck_assert_int_eq(been_here, QB_FALSE); been_here = QB_TRUE; } static qb_loop_timer_handle reset_th; static int32_t reset_timer_step = 0; static void reset_one_shot_tmo(void*data) { int32_t res; qb_loop_t *l = data; if (reset_timer_step == 0) { res = qb_loop_timer_del(l, reset_th); ck_assert_int_eq(res, -EINVAL); res = qb_loop_timer_is_running(l, reset_th); ck_assert_int_eq(res, QB_FALSE); res = qb_loop_timer_add(l, QB_LOOP_LOW, 8*QB_TIME_NS_IN_MSEC, l, reset_one_shot_tmo, &reset_th); ck_assert_int_eq(res, 0); } reset_timer_step++; } START_TEST(test_loop_timer_basic) { int32_t res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); res = qb_loop_timer_add(l, QB_LOOP_LOW, 5*QB_TIME_NS_IN_MSEC, l, one_shot_tmo, &test_th); ck_assert_int_eq(res, 0); res = qb_loop_timer_is_running(l, test_th); ck_assert_int_eq(res, QB_TRUE); res = qb_loop_timer_add(l, QB_LOOP_LOW, 7*QB_TIME_NS_IN_MSEC, l, reset_one_shot_tmo, &reset_th); ck_assert_int_eq(res, 0); res = qb_loop_timer_add(l, QB_LOOP_HIGH, 20*QB_TIME_NS_IN_MSEC, l, check_time_left, &test_th2); ck_assert_int_eq(res, 0); res = qb_loop_timer_add(l, QB_LOOP_LOW, 60*QB_TIME_NS_IN_MSEC, l, job_stop, &test_th); ck_assert_int_eq(res, 0); qb_loop_run(l); ck_assert_int_eq(reset_timer_step, 2); qb_loop_destroy(l); } END_TEST static void *loop_timer_thread(void *arg) { int res; qb_loop_t *l = (qb_loop_t *)arg; qb_loop_timer_handle test_tht; 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); return (void *)0; } /* This test will probably never fail (unless something really bad happens) but is useful for running under helgrind to find threading issues */ START_TEST(test_loop_timer_threads) { int32_t res; pthread_t thr; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); res = pthread_create(&thr, NULL, loop_timer_thread, l); res = qb_loop_timer_add(l, QB_LOOP_LOW, 7*QB_TIME_NS_IN_MSEC, l, reset_one_shot_tmo, &reset_th); ck_assert_int_eq(res, 0); res = qb_loop_timer_add(l, QB_LOOP_HIGH, 20*QB_TIME_NS_IN_MSEC, l, check_time_left, &test_th2); ck_assert_int_eq(res, 0); res = qb_loop_timer_add(l, QB_LOOP_LOW, 60*QB_TIME_NS_IN_MSEC, l, job_stop, &test_th); ck_assert_int_eq(res, 0); qb_loop_run(l); ck_assert_int_eq(reset_timer_step, 2); pthread_join(thr, NULL); qb_loop_destroy(l); } END_TEST struct qb_stop_watch { uint64_t start; uint64_t end; qb_loop_t *l; uint64_t ns_timer; int64_t total; int32_t count; int32_t killer; qb_loop_timer_handle th; }; static void stop_watch_tmo(void*data) { struct qb_stop_watch *sw = (struct qb_stop_watch *)data; float per; int64_t diff; sw->end = qb_util_nano_current_get(); diff = sw->end - sw->start; if (diff < sw->ns_timer) { printf("timer expired early! by %"PRIi64"\n", (int64_t)(sw->ns_timer - diff)); } ck_assert(diff >= sw->ns_timer); sw->total += diff; sw->total -= sw->ns_timer; sw->start = sw->end; sw->count++; if (sw->count < 50) { qb_loop_timer_add(sw->l, QB_LOOP_LOW, sw->ns_timer, data, stop_watch_tmo, &sw->th); } else { per = ((sw->total * 100) / sw->count) / (float)sw->ns_timer; printf("average error for %"PRIu64" ns timer is %"PRIi64" (ns) (%f)\n", sw->ns_timer, (int64_t)(sw->total/sw->count), per); if (sw->killer) { qb_loop_stop(sw->l); } } } static void start_timer(qb_loop_t *l, struct qb_stop_watch *sw, uint64_t timeout, int32_t killer) { int32_t res; sw->l = l; sw->count = 0; sw->total = 0; sw->killer = killer; sw->ns_timer = timeout; sw->start = qb_util_nano_current_get(); res = qb_loop_timer_add(sw->l, QB_LOOP_LOW, sw->ns_timer, sw, stop_watch_tmo, &sw->th); ck_assert_int_eq(res, 0); } START_TEST(test_loop_timer_precision) { int32_t i; uint64_t tmo; struct qb_stop_watch sw[11]; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); for (i = 0; i < 10; i++) { tmo = ((1 + i * 9) * QB_TIME_NS_IN_MSEC) + 500000; start_timer(l, &sw[i], tmo, QB_FALSE); } start_timer(l, &sw[i], 100 * QB_TIME_NS_IN_MSEC, QB_TRUE); qb_loop_run(l); qb_loop_destroy(l); } END_TEST static int expire_leak_counter = 0; #define EXPIRE_NUM_RUNS 10 static int expire_leak_runs = 0; static void empty_func_tmo(void*data) { expire_leak_counter++; } static void stop_func_tmo(void*data) { qb_loop_t *l = (qb_loop_t *)data; qb_log(LOG_DEBUG, "expire_leak_counter:%d", expire_leak_counter); qb_loop_stop(l); } static void next_func_tmo(void*data) { qb_loop_t *l = (qb_loop_t *)data; int32_t i; uint64_t tmo; uint64_t max_tmo = 0; qb_loop_timer_handle th; qb_log(LOG_DEBUG, "expire_leak_counter:%d", expire_leak_counter); for (i = 0; i < 300; i++) { tmo = ((1 + i) * QB_TIME_NS_IN_MSEC) + 500000; qb_loop_timer_add(l, QB_LOOP_LOW, tmo, NULL, empty_func_tmo, &th); qb_loop_timer_add(l, QB_LOOP_MED, tmo, NULL, empty_func_tmo, &th); qb_loop_timer_add(l, QB_LOOP_HIGH, tmo, NULL, empty_func_tmo, &th); max_tmo = QB_MAX(max_tmo, tmo); } expire_leak_runs++; if (expire_leak_runs == EXPIRE_NUM_RUNS) { qb_loop_timer_add(l, QB_LOOP_LOW, max_tmo, l, stop_func_tmo, &th); } else { qb_loop_timer_add(l, QB_LOOP_LOW, max_tmo, l, next_func_tmo, &th); } } /* * make sure that file descriptors don't get leaked with no qb_loop_timer_del() */ START_TEST(test_loop_timer_expire_leak) { int32_t i; uint64_t tmo; uint64_t max_tmo = 0; qb_loop_timer_handle th; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); expire_leak_counter = 0; for (i = 0; i < 300; i++) { tmo = ((1 + i) * QB_TIME_NS_IN_MSEC) + 500000; qb_loop_timer_add(l, QB_LOOP_LOW, tmo, NULL, empty_func_tmo, &th); qb_loop_timer_add(l, QB_LOOP_MED, tmo, NULL, empty_func_tmo, &th); qb_loop_timer_add(l, QB_LOOP_HIGH, tmo, NULL, empty_func_tmo, &th); max_tmo = QB_MAX(max_tmo, tmo); } qb_loop_timer_add(l, QB_LOOP_LOW, max_tmo, l, next_func_tmo, &th); expire_leak_runs = 1; qb_loop_run(l); ck_assert_int_eq(expire_leak_counter, 300*3* EXPIRE_NUM_RUNS); qb_loop_destroy(l); } END_TEST static int received_signum = 0; static int received_sigs = 0; static int32_t sig_handler(int32_t rsignal, void *data) { qb_loop_t *l = (qb_loop_t *)data; qb_log(LOG_DEBUG, "caught signal %d", rsignal); received_signum = rsignal; received_sigs++; qb_loop_job_add(l, QB_LOOP_LOW, NULL, job_stop); return 0; } START_TEST(test_loop_sig_handling) { qb_loop_signal_handle handle; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); qb_loop_signal_add(l, QB_LOOP_HIGH, SIGINT, l, sig_handler, &handle); qb_loop_signal_add(l, QB_LOOP_HIGH, SIGTERM, l, sig_handler, &handle); qb_loop_signal_add(l, QB_LOOP_HIGH, SIGQUIT, l, sig_handler, &handle); kill(getpid(), SIGINT); qb_loop_run(l); ck_assert_int_eq(received_signum, SIGINT); kill(getpid(), SIGQUIT); qb_loop_run(l); ck_assert_int_eq(received_signum, SIGQUIT); qb_loop_destroy(l); } END_TEST /* Globals for this test only */ static int our_signal_called = 0; static qb_loop_t *this_l; static void handle_nonqb_signal(int num) { our_signal_called = 1; qb_loop_job_add(this_l, QB_LOOP_LOW, NULL, job_stop); } START_TEST(test_loop_dont_override_other_signals) { qb_loop_signal_handle handle; this_l = qb_loop_create(); ck_assert(this_l != NULL); signal(SIGUSR1, handle_nonqb_signal); qb_loop_signal_add(this_l, QB_LOOP_HIGH, SIGINT, this_l, sig_handler, &handle); kill(getpid(), SIGUSR1); qb_loop_run(this_l); ck_assert_int_eq(our_signal_called, 1); qb_loop_destroy(this_l); } END_TEST START_TEST(test_loop_sig_only_get_one) { int res; qb_loop_signal_handle handle; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); /* make sure we only get one call to the handler * don't assume we are going to exit the loop. */ received_sigs = 0; qb_loop_signal_add(l, QB_LOOP_LOW, SIGINT, l, sig_handler, &handle); res = qb_loop_job_add(l, QB_LOOP_MED, NULL, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, NULL, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, NULL, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, NULL, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, NULL, job_1); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_MED, NULL, job_1); ck_assert_int_eq(res, 0); kill(getpid(), SIGINT); qb_loop_run(l); ck_assert_int_eq(received_signum, SIGINT); ck_assert_int_eq(received_sigs, 1); qb_loop_destroy(l); } END_TEST static qb_loop_signal_handle sig_hdl; static void job_rm_sig_handler(void *data) { int res; qb_loop_t *l = (qb_loop_t *)data; res = qb_loop_signal_del(l, sig_hdl); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_LOW, NULL, job_stop); ck_assert_int_eq(res, 0); } START_TEST(test_loop_sig_delete) { int res; qb_loop_t *l = qb_loop_create(); ck_assert(l != NULL); /* make sure we can remove a signal job from the job queue. */ received_sigs = 0; received_signum = 0; res = qb_loop_signal_add(l, QB_LOOP_MED, SIGINT, l, sig_handler, &sig_hdl); ck_assert_int_eq(res, 0); res = qb_loop_job_add(l, QB_LOOP_HIGH, NULL, job_rm_sig_handler); ck_assert_int_eq(res, 0); kill(getpid(), SIGINT); qb_loop_run(l); ck_assert_int_eq(received_sigs, 0); ck_assert_int_eq(received_signum, 0); qb_loop_destroy(l); } END_TEST static Suite * loop_timer_suite(void) { TCase *tc; Suite *s = suite_create("loop_timers"); add_tcase(s, tc, test_loop_timer_input); add_tcase(s, tc, test_loop_timer_basic, 30); add_tcase(s, tc, test_loop_timer_precision, 30); add_tcase(s, tc, test_loop_timer_expire_leak, 30); add_tcase(s, tc, test_loop_timer_threads, 30); return s; } static Suite * loop_signal_suite(void) { TCase *tc; Suite *s = suite_create("loop_signal_suite"); add_tcase(s, tc, test_loop_sig_handling, 10); add_tcase(s, tc, test_loop_sig_only_get_one); add_tcase(s, tc, test_loop_sig_delete); add_tcase(s, tc, test_loop_dont_override_other_signals); return s; } int32_t main(void) { int32_t number_failed; SRunner *sr = srunner_create(loop_job_suite()); srunner_add_suite (sr, loop_timer_suite()); srunner_add_suite (sr, loop_signal_suite()); 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; } diff --git a/tests/check_tlist.c b/tests/check_tlist.c new file mode 100644 index 0000000..1fecec0 --- /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; +}