| /* |
| * |
| * Thread-safe Dictionary Using String Identifiers |
| * Copyright 1998-2000 Scott Shambarger (scott@shambarger.net) |
| * |
| * This software is open source. Permission to use, copy, modify, and |
| * distribute this software for any purpose and without fee is hereby granted, |
| * provided that the above copyright notice appear in all copies. No |
| * warranty of any kind is expressed or implied. Use at your own risk. |
| * |
| */ |
| |
| #include "cs_config.h" |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <assert.h> |
| |
| #include "neo_misc.h" |
| #include "neo_err.h" |
| #include "dict.h" |
| #include "skiplist.h" |
| #include "ulocks.h" |
| |
| |
| typedef struct dictValue { |
| |
| void *value; /* value to set/update */ |
| dictNewValueCB new; /* new value callback (value is NULL) */ |
| dictUpdateValueCB update; /* update value callback (value is NULL) */ |
| void *rock; /* rock to pass to callbacks */ |
| |
| } *dictValuePtr; |
| |
| typedef struct dictItem { |
| |
| struct dictItem *next; /* pointer to next value */ |
| char *id; /* string id */ |
| void *value; /* value */ |
| |
| } *dictItemPtr; |
| |
| typedef struct dictEntry { |
| |
| dictItemPtr first; /* first item in entry */ |
| BOOL deleted; /* TRUE if entry has been passed to skipDelete */ |
| |
| } *dictEntryPtr; |
| |
| typedef UINT32 (*dictHashFunc)(const char *str); |
| typedef int (*dictCompFunc)(const char *s1, const char *s2); |
| |
| struct _dictCtx { |
| |
| pthread_mutex_t mList; /* list update mutex */ |
| skipList list; /* skip list */ |
| |
| dictHashFunc hash; /* hash function */ |
| dictCompFunc comp; /* id compare function */ |
| BOOL useCase; |
| |
| BOOL threaded; /* TRUE if threaded */ |
| dictFreeValueFunc freeValue; /* free value callback */ |
| void *freeRock; /* context for freeValue */ |
| }; |
| |
| #undef DO_DEBUG |
| |
| #ifdef DO_DEBUG |
| #include <sched.h> |
| #define DICT_LOCK(dict) \ |
| do { if((dict)->threaded) { sched_yield(); \ |
| mLock(&(dict)->mList); } } while(0) |
| #define DICT_HASH_BITS 16 |
| #else |
| #define DICT_LOCK(dict) \ |
| if((dict)->threaded) mLock(&(dict)->mList) |
| #define DICT_HASH_BITS 65536 |
| #endif |
| #define DICT_UNLOCK(dict) \ |
| if((dict)->threaded) mUnlock(&(dict)->mList) |
| |
| /* entry is locked, so item may be added */ |
| static NEOERR *dictNewItem(dictCtx dict, dictEntryPtr entry, |
| const char *id, dictValuePtr newval, dictItemPtr *item) |
| { |
| dictItemPtr my_item; |
| |
| if (item != NULL) |
| *item = NULL; |
| |
| /* check if we can set a new value */ |
| if(! (newval->value || newval->new)) |
| return nerr_raise(NERR_ASSERT, "value or new are NULL"); |
| |
| if(! (my_item = calloc(1, sizeof(struct dictItem)))) |
| return nerr_raise(NERR_NOMEM, "Unable to allocate new dictItem"); |
| |
| if(! (my_item->id = strdup(id))) { |
| free(my_item); |
| return nerr_raise(NERR_NOMEM, "Unable to allocate new id for dictItem"); |
| } |
| |
| /* set new value */ |
| if(newval->value) { |
| my_item->value = newval->value; |
| } |
| else { |
| NEOERR *err = STATUS_OK; |
| |
| err = newval->new(id, newval->rock, &(my_item->value)); |
| if (err != STATUS_OK) |
| { |
| /* new item callback failed, cleanup */ |
| free(my_item->id); |
| free(my_item); |
| |
| return nerr_pass(err); |
| } |
| } |
| |
| my_item->next = entry->first; |
| entry->first = my_item; |
| if (item != NULL) |
| *item = my_item; |
| |
| return STATUS_OK; |
| } |
| |
| static void dictFreeItem(dictCtx dict, dictItemPtr item) { |
| |
| if(dict->freeValue) |
| dict->freeValue(item->value, dict->freeRock); |
| free(item->id); |
| free(item); |
| |
| return; |
| } |
| |
| /* list locked, so safe to walk entry */ |
| static dictItemPtr dictFindItem(dictCtx dict, dictEntryPtr entry, |
| const char *id, BOOL unlink) { |
| |
| dictItemPtr *prev, item; |
| |
| prev = &entry->first; |
| |
| for(item = entry->first; item; item = item->next) { |
| |
| if(! dict->comp(item->id, id)) { |
| |
| if(unlink) |
| *prev = item->next; |
| |
| return item; |
| } |
| |
| prev = &item->next; |
| } |
| |
| return NULL; |
| } |
| |
| static NEOERR *dictUpdate(dictCtx dict, dictEntryPtr entry, const char *id, |
| dictValuePtr newval, void *lock) { |
| |
| NEOERR *err = STATUS_OK; |
| dictItemPtr item = NULL; |
| void *newValue; |
| |
| /* check for entry (maybe not found...) */ |
| if(! entry) |
| return nerr_raise(NERR_NOT_FOUND, "Entry is NULL"); |
| |
| /* only use entry if not deleted */ |
| if(! entry->deleted) { |
| |
| /* find item */ |
| if((item = dictFindItem(dict, entry, id, FALSE))) { |
| |
| if(newval->value) { |
| |
| if(dict->freeValue) |
| dict->freeValue(item->value, dict->freeRock); |
| |
| item->value = newval->value; |
| } |
| else if(newval->update) { |
| |
| /* track error (if update fails) */ |
| err = newval->update(id, item->value, newval->rock); |
| } |
| else if((err = newval->new(id, newval->rock, &newValue)) == STATUS_OK) { |
| |
| if(dict->freeValue) |
| dict->freeValue(item->value, dict->freeRock); |
| |
| item->value = newValue; |
| } |
| else { |
| /* new item failed (don't remove old), indicate that update failed */ |
| item = NULL; |
| } |
| } |
| else { |
| |
| /* add new item to entry */ |
| err = dictNewItem(dict, entry, id, newval, &item); |
| } |
| } |
| |
| /* release entry lock */ |
| skipRelease(dict->list, lock); |
| |
| return nerr_pass(err); |
| } |
| |
| static NEOERR *dictInsert(dictCtx dict, UINT32 hash, const char *id, |
| dictValuePtr newval) { |
| |
| dictEntryPtr entry; |
| void *lock; |
| NEOERR *err = STATUS_OK; |
| |
| /* create new item and insert entry */ |
| entry = calloc(1, sizeof(struct dictEntry)); |
| if (entry == NULL) |
| return nerr_raise(NERR_NOMEM, "Unable to allocate memory for dictEntry"); |
| |
| /* create/insert item (or cleanup) */ |
| err = dictNewItem(dict, entry, id, newval, NULL); |
| if (err != STATUS_OK) return nerr_pass(err); |
| |
| /* if we insert, we're done */ |
| if((err = skipInsert(dict->list, hash, entry, FALSE)) == STATUS_OK) |
| return STATUS_OK; |
| |
| /* failed to insert, cleanup */ |
| if(dict->freeValue && ! newval->value) |
| dict->freeValue(entry->first->value, dict->freeRock); |
| free(entry->first->id); |
| free(entry->first); |
| free(entry); |
| |
| /* check err */ |
| if (!nerr_handle(&err, NERR_DUPLICATE)) |
| return nerr_pass(err); |
| |
| /* cool, someone already inserted the entry before we got the lock */ |
| entry = skipSearch(dict->list, hash, &lock); |
| |
| /* update entry as normal (handles entry not found) */ |
| return nerr_pass(dictUpdate(dict, entry, id, newval, lock)); |
| } |
| |
| static UINT32 dictHash(dictCtx dict, const char *id) { |
| |
| UINT32 hash; |
| |
| hash = dict->hash(id) % DICT_HASH_BITS; |
| |
| /* ensure hash is valid for skiplist (modify consistently if not) */ |
| if(! (hash && (hash != (UINT32)-1))) |
| hash = 1; |
| |
| return hash; |
| } |
| |
| static NEOERR *dictModify(dictCtx dict, const char *id, dictValuePtr newval) |
| { |
| NEOERR *err; |
| UINT32 hash; |
| dictEntryPtr entry; |
| void *lock = NULL; |
| |
| hash = dictHash(dict, id); |
| |
| /* find entry in list */ |
| entry = skipSearch(dict->list, hash, &lock); |
| |
| DICT_LOCK(dict); |
| |
| if((err = dictUpdate(dict, entry, id, newval, lock)) != STATUS_OK) |
| { |
| /* insert new entry */ |
| nerr_ignore(&err); |
| err = dictInsert(dict, hash, id, newval); |
| } |
| |
| DICT_UNLOCK(dict); |
| |
| return nerr_pass(err); |
| } |
| |
| NEOERR *dictSetValue(dictCtx dict, const char *id, void *value) { |
| |
| struct dictValue newval; |
| |
| assert(value); |
| |
| newval.value = value; |
| |
| return dictModify(dict, id, &newval); |
| } |
| |
| NEOERR *dictModifyValue(dictCtx dict, const char *id, dictNewValueCB new, |
| dictUpdateValueCB update, void *rock) { |
| |
| struct dictValue newval; |
| |
| if(! (new || update)) |
| return FALSE; |
| |
| newval.value = NULL; |
| newval.new = new; |
| newval.update = update; |
| newval.rock = rock; |
| |
| return dictModify(dict, id, &newval); |
| } |
| |
| void dictReleaseLock(dictCtx dict, void *lock) { |
| |
| /* release entry */ |
| DICT_UNLOCK(dict); |
| |
| /* release skip entry */ |
| skipRelease(dict->list, lock); |
| |
| return; |
| } |
| |
| void dictCleanup(dictCtx dict, dictCleanupFunc cleanup, void *rock) { |
| |
| dictItemPtr *prev, item, next; |
| dictEntryPtr entry; |
| UINT32 key = 0; |
| void *lock; |
| |
| while((entry = skipNext(dict->list, &key, &lock))) { |
| |
| DICT_LOCK(dict); |
| |
| prev = &entry->first; |
| |
| for(item = entry->first; item; item = next) { |
| |
| next = item->next; |
| |
| if(cleanup(item->id, item->value, rock)) { |
| |
| /* remove item */ |
| *prev = item->next; |
| dictFreeItem(dict, item); |
| } |
| else { |
| /* update reference pointer */ |
| prev = &item->next; |
| } |
| } |
| |
| /* delete entry if last item removed */ |
| if(! entry->first) { |
| |
| entry->deleted = TRUE; |
| |
| skipDelete(dict->list, key); |
| } |
| |
| dictReleaseLock(dict, lock); |
| } |
| |
| return; |
| } |
| |
| void *dictSearch(dictCtx dict, const char *id, void **plock) { |
| |
| dictEntryPtr entry; |
| dictItemPtr item; |
| UINT32 hash; |
| void *lock; |
| void *value; |
| |
| hash = dictHash(dict, id); |
| |
| /* find entry in list */ |
| if(! (entry = skipSearch(dict->list, hash, &lock))) |
| return NULL; |
| |
| /* lock entry */ |
| DICT_LOCK(dict); |
| |
| /* find item */ |
| if((item = dictFindItem(dict, entry, id, FALSE))) { |
| |
| value = item->value; |
| |
| if(plock) |
| *plock = lock; |
| else |
| dictReleaseLock(dict, lock); |
| |
| return value; |
| } |
| |
| dictReleaseLock(dict, lock); |
| |
| return NULL; |
| } |
| |
| void *dictNext (dictCtx dict, char **id, void **plock) |
| { |
| dictEntryPtr entry; |
| dictItemPtr item; |
| UINT32 hash; |
| void *lock; |
| void *value; |
| |
| /* Handle the first one special case */ |
| if (*id == NULL) |
| { |
| hash = 0; |
| /* find entry in list */ |
| if(! (entry = skipNext (dict->list, &hash, &lock))) |
| return NULL; |
| |
| /* lock entry */ |
| DICT_LOCK(dict); |
| |
| /* Take first item in list */ |
| item = entry->first; |
| |
| if (item != NULL) |
| { |
| value = item->value; |
| *id = item->id; |
| |
| if(plock) |
| *plock = lock; |
| else |
| dictReleaseLock(dict, lock); |
| |
| return value; |
| } |
| |
| dictReleaseLock(dict, lock); |
| |
| return NULL; |
| } |
| else |
| { |
| hash = dictHash(dict, *id); |
| |
| /* find entry in list */ |
| entry = skipSearch (dict->list, hash, &lock); |
| |
| if (entry == NULL) |
| { |
| entry = skipNext (dict->list, &hash, &lock); |
| /* Not found, we're at the end of the dict */ |
| if (entry == NULL) |
| return NULL; |
| } |
| |
| /* lock entry */ |
| DICT_LOCK(dict); |
| |
| item = dictFindItem(dict, entry, *id, FALSE); |
| if (item != NULL) |
| { |
| if (item->next != NULL) |
| { |
| item = item->next; |
| } |
| else |
| { |
| /* we have to move to the next skip entry */ |
| entry = skipNext (dict->list, &hash, &lock); |
| /* Not found, we're at the end of the dict */ |
| item = entry?entry->first:NULL; |
| |
| if(! item) { |
| dictReleaseLock(dict, lock); |
| return NULL; |
| } |
| |
| } |
| value = item->value; |
| *id = item->id; |
| |
| if(plock) |
| *plock = lock; |
| else |
| dictReleaseLock(dict, lock); |
| |
| return value; |
| } |
| |
| dictReleaseLock(dict, lock); |
| } |
| |
| return NULL; |
| } |
| |
| BOOL dictRemove(dictCtx dict, const char *id) { |
| |
| dictEntryPtr entry; |
| dictItemPtr item; |
| UINT32 hash; |
| void *lock; |
| |
| hash = dictHash(dict, id); |
| |
| /* find entry in list */ |
| if(! (entry = skipSearch(dict->list, hash, &lock))) |
| return FALSE; |
| |
| /* lock entry */ |
| DICT_LOCK(dict); |
| |
| /* find/unlink/free item */ |
| if((item = dictFindItem(dict, entry, id, TRUE))) |
| dictFreeItem(dict, item); |
| |
| dictReleaseLock(dict, lock); |
| |
| return item ? TRUE : FALSE; |
| } |
| |
| /* called by skipList when safe to destroy entry */ |
| static void dictDestroyEntry(void *value, void *ctx) { |
| |
| dictItemPtr item, next; |
| dictEntryPtr entry; |
| |
| entry = value; |
| |
| for(item = entry->first; item; item = next) { |
| |
| next = item->next; |
| dictFreeItem(ctx, item); |
| item = next; |
| } |
| |
| free(value); |
| |
| return; |
| } |
| |
| NEOERR *dictCreate(dictCtx *rdict, BOOL threaded, UINT32 root, UINT32 maxLevel, |
| UINT32 flushLimit, BOOL useCase, dictFreeValueFunc freeValue, void *freeRock) |
| { |
| NEOERR *err; |
| dictCtx dict; |
| |
| *rdict = NULL; |
| |
| do { |
| |
| if(! (dict = calloc(1, sizeof(struct _dictCtx)))) |
| return nerr_raise (NERR_NOMEM, "Unable to allocate memory for dictCtx"); |
| |
| dict->useCase = useCase; |
| dict->hash = python_string_hash; |
| if(useCase) { |
| dict->comp = strcmp; |
| } |
| else { |
| /* dict->hash = uhashUpper; */ |
| dict->comp = strcasecmp; |
| } |
| |
| dict->threaded = threaded; |
| dict->freeValue = freeValue; |
| dict->freeRock = freeRock; |
| |
| err = skipNewList(&(dict->list), threaded, root, maxLevel, |
| flushLimit, dictDestroyEntry, dict); |
| if (err != STATUS_OK) break; |
| |
| if (threaded) |
| { |
| err = mCreate(&(dict->mList)); |
| if (err != STATUS_OK) break; |
| } |
| |
| *rdict = dict; |
| return STATUS_OK; |
| |
| } while(FALSE); |
| |
| dictDestroy(dict); |
| |
| return nerr_pass(err); |
| } |
| |
| void dictDestroy(dictCtx dict) { |
| |
| if(! dict) |
| return; |
| |
| skipFreeList(dict->list); |
| |
| mDestroy(&dict->mList); |
| |
| free(dict); |
| |
| return; |
| } |