Home | History | Annotate | Download | only in util
      1 /*
      2  *
      3  * Thread-safe Dictionary Using String Identifiers
      4  * Copyright 1998-2000 Scott Shambarger (scott (at) shambarger.net)
      5  *
      6  * This software is open source. Permission to use, copy, modify, and
      7  * distribute this software for any purpose and without fee is hereby granted,
      8  * provided that the above copyright notice appear in all copies.  No
      9  * warranty of any kind is expressed or implied.  Use at your own risk.
     10  *
     11  */
     12 
     13 #include "cs_config.h"
     14 
     15 #include <string.h>
     16 #include <stdlib.h>
     17 #include <stdio.h>
     18 #include <assert.h>
     19 
     20 #include "neo_misc.h"
     21 #include "neo_err.h"
     22 #include "dict.h"
     23 #include "skiplist.h"
     24 #include "ulocks.h"
     25 
     26 
     27 typedef struct dictValue {
     28 
     29   void *value;                                        /* value to set/update */
     30   dictNewValueCB new;                  /* new value callback (value is NULL) */
     31   dictUpdateValueCB update;         /* update value callback (value is NULL) */
     32   void *rock;                                   /* rock to pass to callbacks */
     33 
     34 } *dictValuePtr;
     35 
     36 typedef struct dictItem {
     37 
     38   struct dictItem *next;                            /* pointer to next value */
     39   char *id;                                                     /* string id */
     40   void *value;                                                      /* value */
     41 
     42 } *dictItemPtr;
     43 
     44 typedef struct dictEntry {
     45 
     46   dictItemPtr first;                                  /* first item in entry */
     47   BOOL deleted;               /* TRUE if entry has been passed to skipDelete */
     48 
     49 } *dictEntryPtr;
     50 
     51 typedef UINT32 (*dictHashFunc)(const char *str);
     52 typedef int (*dictCompFunc)(const char *s1, const char *s2);
     53 
     54 struct _dictCtx {
     55 
     56   pthread_mutex_t mList;                                /* list update mutex */
     57   skipList list;                                                /* skip list */
     58 
     59   dictHashFunc hash;                                        /* hash function */
     60   dictCompFunc comp;                                  /* id compare function */
     61   BOOL useCase;
     62 
     63   BOOL threaded;                                         /* TRUE if threaded */
     64   dictFreeValueFunc freeValue;                        /* free value callback */
     65   void *freeRock;                                   /* context for freeValue */
     66 };
     67 
     68 #undef DO_DEBUG
     69 
     70 #ifdef DO_DEBUG
     71 #include <sched.h>
     72 #define DICT_LOCK(dict) \
     73   do { if((dict)->threaded) { sched_yield(); \
     74    mLock(&(dict)->mList); } } while(0)
     75 #define DICT_HASH_BITS 16
     76 #else
     77 #define DICT_LOCK(dict) \
     78   if((dict)->threaded) mLock(&(dict)->mList)
     79 #define DICT_HASH_BITS 65536
     80 #endif
     81 #define DICT_UNLOCK(dict) \
     82   if((dict)->threaded) mUnlock(&(dict)->mList)
     83 
     84 /* entry is locked, so item may be added */
     85 static NEOERR *dictNewItem(dictCtx dict, dictEntryPtr entry,
     86     const char *id, dictValuePtr newval, dictItemPtr *item)
     87 {
     88   dictItemPtr my_item;
     89 
     90   if (item != NULL)
     91     *item = NULL;
     92 
     93   /* check if we can set a new value */
     94   if(! (newval->value || newval->new))
     95     return nerr_raise(NERR_ASSERT, "value or new are NULL");
     96 
     97   if(! (my_item = calloc(1, sizeof(struct dictItem))))
     98     return nerr_raise(NERR_NOMEM, "Unable to allocate new dictItem");
     99 
    100   if(! (my_item->id = strdup(id))) {
    101     free(my_item);
    102     return nerr_raise(NERR_NOMEM, "Unable to allocate new id for dictItem");
    103   }
    104 
    105   /* set new value */
    106   if(newval->value) {
    107     my_item->value = newval->value;
    108   }
    109   else {
    110     NEOERR *err = STATUS_OK;
    111 
    112     err = newval->new(id, newval->rock, &(my_item->value));
    113     if (err != STATUS_OK)
    114     {
    115       /* new item callback failed, cleanup */
    116       free(my_item->id);
    117       free(my_item);
    118 
    119       return nerr_pass(err);
    120     }
    121   }
    122 
    123   my_item->next = entry->first;
    124   entry->first = my_item;
    125   if (item != NULL)
    126     *item = my_item;
    127 
    128   return STATUS_OK;
    129 }
    130 
    131 static void dictFreeItem(dictCtx dict, dictItemPtr item) {
    132 
    133   if(dict->freeValue)
    134     dict->freeValue(item->value, dict->freeRock);
    135   free(item->id);
    136   free(item);
    137 
    138   return;
    139 }
    140 
    141 /* list locked, so safe to walk entry */
    142 static dictItemPtr dictFindItem(dictCtx dict, dictEntryPtr entry,
    143                                 const char *id, BOOL unlink) {
    144 
    145   dictItemPtr *prev, item;
    146 
    147   prev = &entry->first;
    148 
    149   for(item = entry->first; item; item = item->next) {
    150 
    151     if(! dict->comp(item->id, id)) {
    152 
    153       if(unlink)
    154         *prev = item->next;
    155 
    156       return item;
    157     }
    158 
    159     prev = &item->next;
    160   }
    161 
    162   return NULL;
    163 }
    164 
    165 static NEOERR *dictUpdate(dictCtx dict, dictEntryPtr entry, const char *id,
    166                        dictValuePtr newval, void *lock) {
    167 
    168   NEOERR *err = STATUS_OK;
    169   dictItemPtr item = NULL;
    170   void *newValue;
    171 
    172   /* check for entry (maybe not found...) */
    173   if(! entry)
    174     return nerr_raise(NERR_NOT_FOUND, "Entry is NULL");
    175 
    176   /* only use entry if not deleted */
    177   if(! entry->deleted) {
    178 
    179     /* find item */
    180     if((item = dictFindItem(dict, entry, id, FALSE))) {
    181 
    182       if(newval->value) {
    183 
    184         if(dict->freeValue)
    185           dict->freeValue(item->value, dict->freeRock);
    186 
    187         item->value = newval->value;
    188       }
    189       else if(newval->update) {
    190 
    191         /* track error (if update fails) */
    192 	err = newval->update(id, item->value, newval->rock);
    193       }
    194       else if((err = newval->new(id, newval->rock, &newValue)) == STATUS_OK) {
    195 
    196         if(dict->freeValue)
    197           dict->freeValue(item->value, dict->freeRock);
    198 
    199         item->value = newValue;
    200       }
    201       else {
    202         /* new item failed (don't remove old), indicate that update failed */
    203         item = NULL;
    204       }
    205     }
    206     else {
    207 
    208       /* add new item to entry */
    209       err = dictNewItem(dict, entry, id, newval, &item);
    210     }
    211   }
    212 
    213   /* release entry lock */
    214   skipRelease(dict->list, lock);
    215 
    216   return nerr_pass(err);
    217 }
    218 
    219 static NEOERR *dictInsert(dictCtx dict, UINT32 hash, const char *id,
    220                           dictValuePtr newval) {
    221 
    222   dictEntryPtr entry;
    223   void *lock;
    224   NEOERR *err = STATUS_OK;
    225 
    226   /* create new item and insert entry */
    227   entry = calloc(1, sizeof(struct dictEntry));
    228   if (entry == NULL)
    229     return nerr_raise(NERR_NOMEM, "Unable to allocate memory for dictEntry");
    230 
    231   /* create/insert item (or cleanup) */
    232   err = dictNewItem(dict, entry, id, newval, NULL);
    233   if (err != STATUS_OK) return nerr_pass(err);
    234 
    235   /* if we insert, we're done */
    236   if((err = skipInsert(dict->list, hash, entry, FALSE)) == STATUS_OK)
    237     return STATUS_OK;
    238 
    239   /* failed to insert, cleanup */
    240   if(dict->freeValue && ! newval->value)
    241     dict->freeValue(entry->first->value, dict->freeRock);
    242   free(entry->first->id);
    243   free(entry->first);
    244   free(entry);
    245 
    246   /* check err */
    247   if (!nerr_handle(&err, NERR_DUPLICATE))
    248     return nerr_pass(err);
    249 
    250   /* cool, someone already inserted the entry before we got the lock */
    251   entry = skipSearch(dict->list, hash, &lock);
    252 
    253   /* update entry as normal (handles entry not found) */
    254   return nerr_pass(dictUpdate(dict, entry, id, newval, lock));
    255 }
    256 
    257 static UINT32 dictHash(dictCtx dict, const char *id) {
    258 
    259   UINT32 hash;
    260 
    261   hash = dict->hash(id) % DICT_HASH_BITS;
    262 
    263   /* ensure hash is valid for skiplist (modify consistently if not) */
    264   if(! (hash && (hash != (UINT32)-1)))
    265     hash = 1;
    266 
    267   return hash;
    268 }
    269 
    270 static NEOERR *dictModify(dictCtx dict, const char *id, dictValuePtr newval)
    271 {
    272   NEOERR *err;
    273   UINT32 hash;
    274   dictEntryPtr entry;
    275   void *lock = NULL;
    276 
    277   hash = dictHash(dict, id);
    278 
    279   /* find entry in list */
    280   entry = skipSearch(dict->list, hash, &lock);
    281 
    282   DICT_LOCK(dict);
    283 
    284   if((err = dictUpdate(dict, entry, id, newval, lock)) != STATUS_OK)
    285   {
    286     /* insert new entry */
    287     nerr_ignore(&err);
    288     err = dictInsert(dict, hash, id, newval);
    289   }
    290 
    291   DICT_UNLOCK(dict);
    292 
    293   return nerr_pass(err);
    294 }
    295 
    296 NEOERR *dictSetValue(dictCtx dict, const char *id, void *value) {
    297 
    298   struct dictValue newval;
    299 
    300   assert(value);
    301 
    302   newval.value = value;
    303 
    304   return dictModify(dict, id, &newval);
    305 }
    306 
    307 NEOERR *dictModifyValue(dictCtx dict, const char *id, dictNewValueCB new,
    308                      dictUpdateValueCB update, void *rock) {
    309 
    310   struct dictValue newval;
    311 
    312   if(! (new || update))
    313     return FALSE;
    314 
    315   newval.value = NULL;
    316   newval.new = new;
    317   newval.update = update;
    318   newval.rock = rock;
    319 
    320   return dictModify(dict, id, &newval);
    321 }
    322 
    323 void dictReleaseLock(dictCtx dict, void *lock) {
    324 
    325   /* release entry */
    326   DICT_UNLOCK(dict);
    327 
    328   /* release skip entry */
    329   skipRelease(dict->list, lock);
    330 
    331   return;
    332 }
    333 
    334 void dictCleanup(dictCtx dict, dictCleanupFunc cleanup, void *rock) {
    335 
    336   dictItemPtr *prev, item, next;
    337   dictEntryPtr entry;
    338   UINT32 key = 0;
    339   void *lock;
    340 
    341   while((entry = skipNext(dict->list, &key, &lock))) {
    342 
    343     DICT_LOCK(dict);
    344 
    345     prev = &entry->first;
    346 
    347     for(item = entry->first; item; item = next) {
    348 
    349       next = item->next;
    350 
    351       if(cleanup(item->id, item->value, rock)) {
    352 
    353         /* remove item */
    354         *prev = item->next;
    355         dictFreeItem(dict, item);
    356       }
    357       else {
    358         /* update reference pointer */
    359         prev = &item->next;
    360       }
    361     }
    362 
    363     /* delete entry if last item removed */
    364     if(! entry->first) {
    365 
    366       entry->deleted = TRUE;
    367 
    368       skipDelete(dict->list, key);
    369     }
    370 
    371     dictReleaseLock(dict, lock);
    372   }
    373 
    374   return;
    375 }
    376 
    377 void *dictSearch(dictCtx dict, const char *id, void **plock) {
    378 
    379   dictEntryPtr entry;
    380   dictItemPtr item;
    381   UINT32 hash;
    382   void *lock;
    383   void *value;
    384 
    385   hash = dictHash(dict, id);
    386 
    387   /* find entry in list */
    388   if(! (entry = skipSearch(dict->list, hash, &lock)))
    389     return NULL;
    390 
    391   /* lock entry */
    392   DICT_LOCK(dict);
    393 
    394   /* find item */
    395   if((item = dictFindItem(dict, entry, id, FALSE))) {
    396 
    397     value = item->value;
    398 
    399     if(plock)
    400       *plock = lock;
    401     else
    402       dictReleaseLock(dict, lock);
    403 
    404     return value;
    405   }
    406 
    407   dictReleaseLock(dict, lock);
    408 
    409   return NULL;
    410 }
    411 
    412 void *dictNext (dictCtx dict, char **id, void **plock)
    413 {
    414   dictEntryPtr entry;
    415   dictItemPtr item;
    416   UINT32 hash;
    417   void *lock;
    418   void *value;
    419 
    420   /* Handle the first one special case */
    421   if (*id == NULL)
    422   {
    423     hash = 0;
    424     /* find entry in list */
    425     if(! (entry = skipNext (dict->list, &hash, &lock)))
    426       return NULL;
    427 
    428     /* lock entry */
    429     DICT_LOCK(dict);
    430 
    431     /* Take first item in list */
    432     item = entry->first;
    433 
    434     if (item != NULL)
    435     {
    436       value = item->value;
    437       *id = item->id;
    438 
    439       if(plock)
    440 	*plock = lock;
    441       else
    442 	dictReleaseLock(dict, lock);
    443 
    444       return value;
    445     }
    446 
    447     dictReleaseLock(dict, lock);
    448 
    449     return NULL;
    450   }
    451   else
    452   {
    453     hash = dictHash(dict, *id);
    454 
    455     /* find entry in list */
    456     entry = skipSearch (dict->list, hash, &lock);
    457 
    458     if (entry == NULL)
    459     {
    460       entry = skipNext (dict->list, &hash, &lock);
    461       /* Not found, we're at the end of the dict */
    462       if (entry == NULL)
    463 	return NULL;
    464     }
    465 
    466     /* lock entry */
    467     DICT_LOCK(dict);
    468 
    469     item = dictFindItem(dict, entry, *id, FALSE);
    470     if (item != NULL)
    471     {
    472       if (item->next != NULL)
    473       {
    474 	item = item->next;
    475       }
    476       else
    477       {
    478 	/* we have to move to the next skip entry */
    479 	entry = skipNext (dict->list, &hash, &lock);
    480 	/* Not found, we're at the end of the dict */
    481         item = entry?entry->first:NULL;
    482 
    483 	if(! item) {
    484           dictReleaseLock(dict, lock);
    485 	  return NULL;
    486         }
    487 
    488       }
    489       value = item->value;
    490       *id = item->id;
    491 
    492       if(plock)
    493 	*plock = lock;
    494       else
    495 	dictReleaseLock(dict, lock);
    496 
    497       return value;
    498     }
    499 
    500     dictReleaseLock(dict, lock);
    501   }
    502 
    503   return NULL;
    504 }
    505 
    506 BOOL dictRemove(dictCtx dict, const char *id) {
    507 
    508   dictEntryPtr entry;
    509   dictItemPtr item;
    510   UINT32 hash;
    511   void *lock;
    512 
    513   hash = dictHash(dict, id);
    514 
    515   /* find entry in list */
    516   if(! (entry = skipSearch(dict->list, hash, &lock)))
    517     return FALSE;
    518 
    519   /* lock entry */
    520   DICT_LOCK(dict);
    521 
    522   /* find/unlink/free item */
    523   if((item = dictFindItem(dict, entry, id, TRUE)))
    524     dictFreeItem(dict, item);
    525 
    526   dictReleaseLock(dict, lock);
    527 
    528   return item ? TRUE : FALSE;
    529 }
    530 
    531 /* called by skipList when safe to destroy entry */
    532 static void dictDestroyEntry(void *value, void *ctx) {
    533 
    534   dictItemPtr item, next;
    535   dictEntryPtr entry;
    536 
    537   entry = value;
    538 
    539   for(item = entry->first; item; item = next) {
    540 
    541     next = item->next;
    542     dictFreeItem(ctx, item);
    543     item = next;
    544   }
    545 
    546   free(value);
    547 
    548   return;
    549 }
    550 
    551 NEOERR *dictCreate(dictCtx *rdict, BOOL threaded, UINT32 root, UINT32 maxLevel,
    552     UINT32 flushLimit, BOOL useCase, dictFreeValueFunc freeValue, void *freeRock)
    553 {
    554   NEOERR *err;
    555   dictCtx dict;
    556 
    557   *rdict = NULL;
    558 
    559   do {
    560 
    561     if(! (dict = calloc(1, sizeof(struct _dictCtx))))
    562       return nerr_raise (NERR_NOMEM, "Unable to allocate memory for dictCtx");
    563 
    564     dict->useCase = useCase;
    565     dict->hash = python_string_hash;
    566     if(useCase) {
    567       dict->comp = strcmp;
    568     }
    569     else {
    570       /* dict->hash = uhashUpper; */
    571       dict->comp = strcasecmp;
    572     }
    573 
    574     dict->threaded = threaded;
    575     dict->freeValue = freeValue;
    576     dict->freeRock = freeRock;
    577 
    578     err = skipNewList(&(dict->list), threaded, root, maxLevel,
    579 	    flushLimit, dictDestroyEntry, dict);
    580     if (err != STATUS_OK) break;
    581 
    582     if (threaded)
    583     {
    584       err = mCreate(&(dict->mList));
    585       if (err != STATUS_OK) break;
    586     }
    587 
    588     *rdict = dict;
    589     return STATUS_OK;
    590 
    591   } while(FALSE);
    592 
    593   dictDestroy(dict);
    594 
    595   return nerr_pass(err);
    596 }
    597 
    598 void dictDestroy(dictCtx dict) {
    599 
    600   if(! dict)
    601     return;
    602 
    603   skipFreeList(dict->list);
    604 
    605   mDestroy(&dict->mList);
    606 
    607   free(dict);
    608 
    609   return;
    610 }
    611