/* * This file is distributed as part of the SkySQL Gateway. It is free * software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, * version 2. * * This program 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright SkySQL Ab 2013 */ #include #include #include #include /** * @file hashtable.c General purpose hashtable routines * * The hashtable can be create with a custom number of hash buckets, * a hash function and optional functions to call make copies of the key * and value and to free them. * * The hashtable is arrange as a set of linked lists, the number of linked * lists beign the hashsize as requested by the user. Entries are hashed by * calling the hash function that is passed in by the user, this is used as * an index into the array of linked lists, usign modulo hashsize. * * The linked lists are searched using the key comparison function that is * passed into the hash table creation routine. * * By default the hash table keeps the original pointers that are passed in * for the keys and values, however two functions can be supplied to copy these * a copy function and a free function. Please note the same function is used for * the key and the value, if the actions required are different the called functions * must understand how to differenate the key and value. * * The hash table implements a single write, multiple reader locking policy by * using a pair of counters and a spinlock. The spinlock is used to protect the * number of readers and writers counters when taking out locks. Releasing of * locks uses pure atomic actions and thus does not require spinlock protection. * * @verbatim * Revision History * * Date Who Description * 23/06/13 Mark Riddoch Initial implementation * 23/07/13 Mark Riddoch Addition of hashtable iterator * * @endverbatim */ static void hashtable_read_lock(HASHTABLE *table); static void hashtable_read_unlock(HASHTABLE *table); static void hashtable_write_lock(HASHTABLE *table); static void hashtable_write_unlock(HASHTABLE *table); /** * Special null function used as default memory allfunctions in the hashtable * implementation. This avoids having to special case the code that manipulates * the keys and values * * @param data The data pointer * @return Return the value we were called with */ static void * nullfn(void *data) { return data; } /** * Allocate a new hash table * * @param size The size of the hash table * @param hashfn The user supplied hash function * @param cmpfn The user supplied key comparison function * @return The hashtable table */ HASHTABLE * hashtable_alloc(int size, int (*hashfn)(), int (*cmpfn)()) { HASHTABLE *rval; if ((rval = malloc(sizeof(HASHTABLE))) == NULL) return NULL; #if defined(SS_DEBUG) rval->ht_chk_top = CHK_NUM_HASHTABLE; rval->ht_chk_tail = CHK_NUM_HASHTABLE; #endif rval->hashsize = size; rval->hashfn = hashfn; rval->cmpfn = cmpfn; rval->copyfn = nullfn; rval->freefn = nullfn; rval->n_readers = 0; rval->writelock = 0; spinlock_init(&rval->spin); if ((rval->entries = (HASHENTRIES **)calloc(size, sizeof(HASHENTRIES *))) == NULL) { free(rval); return NULL; } memset(rval->entries, 0, size * sizeof(HASHENTRIES *)); return rval; } /** * Delete an entire hash table * * @param table The hash table to delete */ void hashtable_free(HASHTABLE *table) { int i; HASHENTRIES *entry, *ptr; hashtable_write_lock(table); for (i = 0; i < table->hashsize; i++) { entry = table->entries[i]; while (entry) { ptr = entry->next; table->freefn(entry->key); table->freefn(entry->value); free(entry); entry = ptr; } } free(table->entries); free(table); } /** * Provide memory management functions to the hash table. This allows * function pointers to be registered that can make copies of the * key and value. * * @param table The hash table * @param copyfn The copy function * @param freefn The free function */ void hashtable_memory_fns(HASHTABLE *table, HASHMEMORYFN copyfn, HASHMEMORYFN freefn) { table->copyfn = copyfn; table->freefn = freefn; } /** * Add an item to the hash table. * * @param table The hash table to which to add the item * @param key The key of the item * @param value The value for the item * @return Return the number of items added */ int hashtable_add(HASHTABLE *table, void *key, void *value) { int hashkey; HASHENTRIES *entry; if (table->hashsize <= 0) { return 0; } else { hashkey = table->hashfn(key) % table->hashsize; } hashtable_write_lock(table); entry = table->entries[hashkey % table->hashsize]; while (entry && table->cmpfn(key, entry->key) != 0) { entry = entry->next; } if (entry && table->cmpfn(key, entry->key) == 0) { /* Duplicate key value */ hashtable_write_unlock(table); return 0; } else { HASHENTRIES *ptr = (HASHENTRIES *)malloc(sizeof(HASHENTRIES)); if (ptr == NULL) { hashtable_write_unlock(table); return 0; } ptr->key = table->copyfn(key); ptr->value = table->copyfn(value); ptr->next = table->entries[hashkey % table->hashsize]; table->entries[hashkey % table->hashsize] = ptr; } hashtable_write_unlock(table); return 1; } /** * Delete an item from the hash table that has a given key * * @param table The hash table to delete from * @param key The key value of the item to remove * @return Return the number of items deleted */ int hashtable_delete(HASHTABLE *table, void *key) { int hashkey = table->hashfn(key) % table->hashsize; HASHENTRIES *entry, *ptr; hashtable_write_lock(table); entry = table->entries[hashkey % table->hashsize]; while (entry && entry->key && table->cmpfn(key, entry->key) != 0) { entry = entry->next; } if (entry == NULL) { /* Not found */ hashtable_write_unlock(table); return 0; } if (entry == table->entries[hashkey % table->hashsize]) { /* We are removing from the first entry */ table->entries[hashkey % table->hashsize] = entry->next; table->freefn(entry->key); table->freefn(entry->value); if (entry->next != NULL) { entry->key = entry->next->key; entry->value = entry->next->value; } else { entry->key = NULL; entry->value = NULL; } free(entry); } else { ptr = table->entries[hashkey % table->hashsize]; while (ptr && ptr->next != entry) ptr = ptr->next; if (ptr == NULL) { hashtable_write_unlock(table); return 0; /* This should never happen */ } ptr->next = entry->next; table->freefn(entry->key); table->freefn(entry->value); free(entry); } hashtable_write_unlock(table); return 1; } /** * Fetch an item with a given key value from the hash table * * @param table The hash table * @param key The key value * @return The item or NULL if the item was not found */ void * hashtable_fetch(HASHTABLE *table, void *key) { int hashkey = table->hashfn(key) % table->hashsize; HASHENTRIES *entry; hashtable_read_lock(table); entry = table->entries[hashkey % table->hashsize]; while (entry && entry->key && table->cmpfn(key, entry->key) != 0) { entry = entry->next; } if (entry == NULL) { hashtable_read_unlock(table); return NULL; } else { hashtable_read_unlock(table); return entry->value; } } /** * Print hash table statistics to the standard output * * @param table The hash table */ void hashtable_stats(HASHTABLE *table) { int total, longest, i, j; HASHENTRIES *entries; printf("Hashtable: %p, size %d\n", table, table->hashsize); total = 0; longest = 0; hashtable_read_lock(table); for (i = 0; i < table->hashsize; i++) { j = 0; entries = table->entries[i]; while (entries) { j++; entries = entries->next; } total += j; if (j > longest) longest = j; } hashtable_read_unlock(table); printf("\tNo. of entries: %d\n", total); printf("\tAverage chain length: %.1f\n", (float)total / table->hashsize); printf("\tLongest chain length: %d\n", longest); } /** * Produces stat output about hashtable * * Parameters: * @param table - * * * @param hashsize - * * * @param nelems - * * * @param longest - * * * @return void * * */ void hashtable_get_stats( void* table, int* hashsize, int* nelems, int* longest) { HASHTABLE* ht; HASHENTRIES* entries; int i; int j; ht = (HASHTABLE *)table; CHK_HASHTABLE(ht); hashtable_read_lock(ht); for (i = 0; i < ht->hashsize; i++) { j = 0; entries = ht->entries[i]; while (entries) { j++; entries = entries->next; } *nelems += j; if (j > *longest) { *longest = j; } } *hashsize = ht->hashsize; hashtable_read_unlock(ht); } /** * Take a read lock on the hashtable. * * The hashtable support multiple readers and a single writer, * we have a spinlock to protect the two counts, n_readers and * writelock. * * We take the hashtable spinlock and then check that writelock * is set to zero. If not we release the spinlock and do dirty * reads of writelock until it goes to 0. Once it is zero we * acquire the spinlock again and test that writelock is still * 0. * * With writelock set to zero we increment n_readers with the * spinlock still held. * * @param table The hashtable to lock. */ static void hashtable_read_lock(HASHTABLE *table) { spinlock_acquire(&table->spin); while (table->writelock) { spinlock_release(&table->spin); while (table->writelock) ; spinlock_acquire(&table->spin); } table->n_readers++; spinlock_release(&table->spin); } /** * Release a previously obtained readlock. * * Simply decrement the n_readers value for the hash table * * @param table The hash table to unlock */ static void hashtable_read_unlock(HASHTABLE *table) { atomic_add(&table->n_readers, -1); } /** * Obtain an exclusive write lock for the hash table. * * We acquire the hashtable spinlock, check for the number of * readers beign zero. If it is not we hold the spinlock and * loop waiting for the n_readers to reach zero. This will prevent * any new readers beign granted access but will not prevent current * readers releasing the read lock. * * Once we have no readers we increment writelock and test if we are * the only writelock holder, if not we repeat the process. We hold * the spinlock throughout the process since both read and write * locks do not require the spinlock to be acquired. * * @param table The table to lock for updates */ static void hashtable_write_lock(HASHTABLE *table) { int available; spinlock_acquire(&table->spin); do { while (table->n_readers) ; available = atomic_add(&table->writelock, 1); if (available != 0) atomic_add(&table->writelock, -1); } while (available != 0); spinlock_release(&table->spin); } /** * Release the write lock on the hash table. * * @param table The hash table to unlock */ static void hashtable_write_unlock(HASHTABLE *table) { atomic_add(&table->writelock, -1); } /** * Create an iterator on a hash table * * @param table The table to ceate an iterator on * @return An iterator to use in future calls */ HASHITERATOR * hashtable_iterator(HASHTABLE *table) { HASHITERATOR *rval; if ((rval = (HASHITERATOR *)malloc(sizeof(HASHITERATOR))) != NULL) { rval->table = table; rval->chain = 0; rval->depth = -1; } return rval; } /** * Return the next key for a hashtable iterator * * @param iter The hashtable iterator * @return The next key value or NULL */ void * hashtable_next(HASHITERATOR *iter) { int i; HASHENTRIES *entries; iter->depth++; while (iter->chain < iter->table->hashsize) { if ((entries = iter->table->entries[iter->chain]) != NULL) { i = 0; hashtable_read_lock(iter->table); while (entries && i < iter->depth) { entries = entries->next; i++; } hashtable_read_unlock(iter->table); if (entries) return entries->key; } iter->depth = 0; iter->chain++; } return NULL; } /** * Free a hashtable iterator * * @param iter The iterator to free */ void hashtable_iterator_free(HASHITERATOR *iter) { free(iter); }