The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* 
 *
 * 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;
}