/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * list.c - generic dynamic list * * This module implements the generic list. See list.h for an explanation * of how to use the list. * * The list is implemented as an array, a starting index into the array, * and an integer giving the length of the list. The list's element i is * not necessarily at array element i, but instead it is found at element * * (start + i) % len * * This is because we need to make it fast to use the list as a queue, * meaning that adding elements to the end and removing them from the * beginning must be very fast. Insertions into the middle of the list * need not be fast, however. It would be possible to implement the list * with a linked list, of course, but this would cause many more memory * allocations: every time an item is added to the list, a new node would * need to be allocated, and when it is removed, it would need to be freed. * Using an array lets us reduce the number of allocations. It also lets * us access an arbitrary element in constant time, which is specially * useful since it lets us simplify the list API by not adding iterators * or an explicit list item type. * * If insertions and deletions into the middle of the list become common, * it would be more efficient to use a buffer gap implementation, but * there's no point in doing that until the need arises. * * Lars Wirzenius */ #include "gw-config.h" #include #include #include #include "gwassert.h" #include "list.h" #include "log.h" #include "thread.h" #include "gwmem.h" struct List { void **tab; long tab_size; long start; long len; Mutex *single_operation_lock; Mutex *permanent_lock; pthread_cond_t nonempty; long num_producers; }; #define INDEX(list, i) (((list)->start + i) % (list)->tab_size) #define GET(list, i) ((list)->tab[INDEX(list, i)]) long gwthread_self(void); static void lock(List *list); static void unlock(List *list); static void make_bigger(List *list, long items); static void delete_items_from_list(List *list, long pos, long count); List *gwlist_create_real(void) { List *list; list = gw_malloc(sizeof(List)); list->tab = NULL; list->tab_size = 0; list->start = 0; list->len = 0; list->single_operation_lock = mutex_create(); list->permanent_lock = mutex_create(); pthread_cond_init(&list->nonempty, NULL); list->num_producers = 0; return list; } void gwlist_destroy(List *list, gwlist_item_destructor_t *destructor) { void *item; if (list == NULL) return; if (destructor != NULL) { while ((item = gwlist_extract_first(list)) != NULL) destructor(item); } mutex_destroy(list->permanent_lock); mutex_destroy(list->single_operation_lock); pthread_cond_destroy(&list->nonempty); gw_free(list->tab); gw_free(list); } long gwlist_len(List *list) { long len; if (list == NULL) return 0; lock(list); len = list->len; unlock(list); return len; } void gwlist_append(List *list, void *item) { lock(list); make_bigger(list, 1); list->tab[INDEX(list, list->len)] = item; ++list->len; pthread_cond_signal(&list->nonempty); unlock(list); } void gwlist_append_unique(List *list, void *item, int (*cmp)(void *, void *)) { void *it; long i; lock(list); it = NULL; for (i = 0; i < list->len; ++i) { it = GET(list, i); if (cmp(it, item)) { break; } } if (i == list->len) { /* not yet in list, so add it */ make_bigger(list, 1); list->tab[INDEX(list, list->len)] = item; ++list->len; pthread_cond_signal(&list->nonempty); } unlock(list); } void gwlist_insert(List *list, long pos, void *item) { long i; lock(list); gw_assert(pos >= 0); gw_assert(pos <= list->len); make_bigger(list, 1); for (i = list->len; i > pos; --i) list->tab[(list->start + i) % list->tab_size] = list->tab[(list->start + i - 1) % list->tab_size]; list->tab[(list->start + pos) % list->tab_size] = item; ++list->len; pthread_cond_signal(&list->nonempty); unlock(list); } void gwlist_delete(List *list, long pos, long count) { lock(list); delete_items_from_list(list, pos, count); unlock(list); } long gwlist_delete_matching(List *list, void *pat, gwlist_item_matches_t *matches) { long i; long count; lock(list); /* XXX this could be made more efficient by noticing consecutive items to be removed, but leave that for later. --liw */ i = 0; count = 0; while (i < list->len) { if (matches(GET(list, i), pat)) { delete_items_from_list(list, i, 1); count++; } else { ++i; } } unlock(list); return count; } long gwlist_delete_equal(List *list, void *item) { long i; long count; lock(list); /* XXX this could be made more efficient by noticing consecutive items to be removed, but leave that for later. --liw */ i = 0; count = 0; while (i < list->len) { if (GET(list, i) == item) { delete_items_from_list(list, i, 1); count++; } else { ++i; } } unlock(list); return count; } void *gwlist_get(List *list, long pos) { void *item; lock(list); gw_assert(pos >= 0); gw_assert(pos < list->len); item = GET(list, pos); unlock(list); return item; } void *gwlist_extract_first(List *list) { void *item; gw_assert(list != NULL); lock(list); if (list->len == 0) item = NULL; else { item = GET(list, 0); delete_items_from_list(list, 0, 1); } unlock(list); return item; } List *gwlist_extract_matching(List *list, void *pat, gwlist_item_matches_t *cmp) { List *new_list; long i; new_list = gwlist_create(); lock(list); i = 0; while (i < list->len) { if (cmp(GET(list, i), pat)) { gwlist_append(new_list, GET(list, i)); delete_items_from_list(list, i, 1); } else ++i; } unlock(list); if (gwlist_len(new_list) == 0) { gwlist_destroy(new_list, NULL); return NULL; } return new_list; } void gwlist_lock(List *list) { gw_assert(list != NULL); mutex_lock(list->permanent_lock); } void gwlist_unlock(List *list) { gw_assert(list != NULL); mutex_unlock(list->permanent_lock); } int gwlist_wait_until_nonempty(List *list) { int ret; lock(list); while (list->len == 0 && list->num_producers > 0) { list->single_operation_lock->owner = -1; pthread_cond_wait(&list->nonempty, &list->single_operation_lock->mutex); list->single_operation_lock->owner = gwthread_self(); } if (list->len > 0) ret = 1; else ret = -1; unlock(list); return ret; } void gwlist_add_producer(List *list) { lock(list); ++list->num_producers; unlock(list); } int gwlist_producer_count(List *list) { int ret; lock(list); ret = list->num_producers; unlock(list); return ret; } void gwlist_remove_producer(List *list) { lock(list); gw_assert(list->num_producers > 0); --list->num_producers; pthread_cond_broadcast(&list->nonempty); unlock(list); } void gwlist_produce(List *list, void *item) { gwlist_append(list, item); } void *gwlist_consume(List *list) { void *item; lock(list); while (list->len == 0 && list->num_producers > 0) { list->single_operation_lock->owner = -1; pthread_cond_wait(&list->nonempty, &list->single_operation_lock->mutex); list->single_operation_lock->owner = gwthread_self(); } if (list->len > 0) { item = GET(list, 0); delete_items_from_list(list, 0, 1); } else { item = NULL; } unlock(list); return item; } void *gwlist_search(List *list, void *pattern, int (*cmp)(void *, void *)) { void *item; long i; lock(list); item = NULL; for (i = 0; i < list->len; ++i) { item = GET(list, i); if (cmp(item, pattern)) { break; } } if (i == list->len) { item = NULL; } unlock(list); return item; } List *gwlist_search_all(List *list, void *pattern, int (*cmp)(void *, void *)) { List *new_list; void *item; long i; new_list = gwlist_create(); lock(list); item = NULL; for (i = 0; i < list->len; ++i) { item = GET(list, i); if (cmp(item, pattern)) gwlist_append(new_list, item); } unlock(list); if (gwlist_len(new_list) == 0) { gwlist_destroy(new_list, NULL); new_list = NULL; } return new_list; } void gwlist_sort(List *list, int(*cmp)(const void *, const void *)) { gw_assert(list != NULL && cmp != NULL); gwlist_lock(list); if (list->len == 0) { /* nothing to sort */ gwlist_unlock(list); return; } qsort(&GET(list, 0), list->len, sizeof(void*), cmp); gwlist_unlock(list); } /*************************************************************************/ static void lock(List *list) { gw_assert(list != NULL); mutex_lock(list->single_operation_lock); } static void unlock(List *list) { gw_assert(list != NULL); mutex_unlock(list->single_operation_lock); } /* * Make the array bigger. It might be more efficient to make the size * bigger than what is explicitly requested. * * Assume list has been locked for a single operation already. */ static void make_bigger(List *list, long items) { long old_size, new_size; long len_at_beginning, len_at_end; if (list->len + items <= list->tab_size) return; old_size = list->tab_size; new_size = old_size + items; list->tab = gw_realloc(list->tab, new_size * sizeof(void *)); list->tab_size = new_size; /* * Now, one of the following situations is in effect * (* is used, empty is unused element): * * Case 1: Used area did not wrap. No action is necessary. * * old_size new_size * v v * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | |*|*|*|*|*|*| | | | | | | | | | | | | | | | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * ^ ^ * start start+len * * Case 2: Used area wrapped, but the part at the beginning * of the array fits into the new area. Action: move part * from beginning to new area. * * old_size new_size * v v * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |*|*| | | | | | | |*|*|*| | | | | | | | | | | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * ^ ^ * start+len start * * Case 3: Used area wrapped, and the part at the beginning * of the array does not fit into the new area. Action: move * as much as will fit from beginning to new area and move * the rest to the beginning. * * old_size new_size * v v * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |*|*|*|*|*|*|*|*|*| | | | | | | | |*|*|*|*| | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * ^ ^ * start+len start */ gw_assert(list->start < old_size || (list->start == 0 && old_size == 0)); if (list->start + list->len > old_size) { len_at_end = old_size - list->start; len_at_beginning = list->len - len_at_end; if (len_at_beginning <= new_size - old_size) { /* This is Case 2. */ memmove(list->tab + old_size, list->tab, len_at_beginning * sizeof(void *)); } else { /* This is Case 3. */ memmove(list->tab + old_size, list->tab, (new_size - old_size) * sizeof(void *)); memmove(list->tab, list->tab + (new_size - old_size), (len_at_beginning - (new_size - old_size)) * sizeof(void *)); } } } /* * Remove items `pos' through `pos+count-1' from list. Assume list has * been locked by caller already. */ static void delete_items_from_list(List *list, long pos, long count) { long i, from, to; gw_assert(pos >= 0); gw_assert(pos < list->len); gw_assert(count >= 0); gw_assert(pos + count <= list->len); /* * There are four cases: * * Case 1: Deletion at beginning of list. Just move start * marker forwards (wrapping it at end of array). No need * to move any items. * * Case 2: Deletion at end of list. Just shorten the length * of the list. No need to move any items. * * Case 3: Deletion in the middle so that the list does not * wrap in the array. Move remaining items at end of list * to the place of the deletion. * * Case 4: Deletion in the middle so that the list does indeed * wrap in the array. Move as many remaining items at the end * of the list as will fit to the end of the array, then move * the rest to the beginning of the array. */ if (pos == 0) { list->start = (list->start + count) % list->tab_size; list->len -= count; } else if (pos + count == list->len) { list->len -= count; } else if (list->start + list->len < list->tab_size) { memmove(list->tab + list->start + pos, list->tab + list->start + pos + count, (list->len - pos - count) * sizeof(void *)); list->len -= count; } else { /* * This is not specially efficient, but it's simple and * works. Faster methods would have to take more special * cases into account. */ for (i = 0; i < list->len - count - pos; ++i) { from = INDEX(list, pos + i + count); to = INDEX(list, pos + i); list->tab[to] = list->tab[from]; } list->len -= count; } }