You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by st...@apache.org on 2002/05/04 21:54:39 UTC

cvs commit: httpd-2.0/modules/experimental cache_hash.c cache_hash.h mod_mem_cache.c mod_cache.dsp

stoddard    02/05/04 12:54:39

  Modified:    modules/experimental mod_mem_cache.c mod_cache.dsp
  Added:       modules/experimental cache_hash.c cache_hash.h
  Log:
  Introduce a dedicated cache hash adt that does not rely on pools. This
  cures a storage leak when garbage collecting expired entries out of the
  hash table. cache_hash is just apr_hash with some of the function removed
  and reimplemented to use malloc/free in place of apr_pool calls.
  
  Revision  Changes    Path
  1.53      +19 -31    httpd-2.0/modules/experimental/mod_mem_cache.c
  
  Index: mod_mem_cache.c
  ===================================================================
  RCS file: /home/cvs/httpd-2.0/modules/experimental/mod_mem_cache.c,v
  retrieving revision 1.52
  retrieving revision 1.53
  diff -u -r1.52 -r1.53
  --- mod_mem_cache.c	3 May 2002 20:13:57 -0000	1.52
  +++ mod_mem_cache.c	4 May 2002 19:54:39 -0000	1.53
  @@ -58,6 +58,7 @@
   
   #define CORE_PRIVATE
   #include "mod_cache.h"
  +#include "cache_hash.h"
   #include "ap_mpm.h"
   #include "apr_thread_mutex.h"
   #if APR_HAVE_UNISTD_H
  @@ -70,13 +71,6 @@
   
   module AP_MODULE_DECLARE_DATA mem_cache_module;
   
  -/* 
  - * XXX
  - * This cache uses apr_hash functions which leak storage when something is removed
  - * from the cache. This can be fixed in the apr_hash functions by making them use
  - * malloc/free rather than pools to manage their storage requirements.
  - */
  -
   typedef enum {
       CACHE_TYPE_FILE = 1,
       CACHE_TYPE_HEAP,
  @@ -103,7 +97,7 @@
   
   typedef struct {
       apr_thread_mutex_t *lock;
  -    apr_hash_t *cacheht;
  +    cache_hash_t *cacheht;
       apr_size_t cache_size;
       apr_size_t object_cnt;
   
  @@ -206,7 +200,7 @@
            * removed the object from the cache (and set obj->cleanup)
            */
           if (!obj->cleanup) {
  -            apr_hash_set(sconf->cacheht, obj->key, strlen(obj->key), NULL);
  +            cache_hash_set(sconf->cacheht, obj->key, strlen(obj->key), NULL);
               sconf->object_cnt--;
               sconf->cache_size -= mobj->m_len;
               obj->cleanup = 1;
  @@ -243,7 +237,7 @@
   static apr_status_t cleanup_cache_mem(void *sconfv)
   {
       cache_object_t *obj;
  -    apr_hash_index_t *hi;
  +    cache_hash_index_t *hi;
       mem_cache_conf *co = (mem_cache_conf*) sconfv;
   
       if (!co) {
  @@ -254,12 +248,12 @@
           apr_thread_mutex_lock(sconf->lock);
       }
       /* Iterate over the cache and clean up each entry */
  -    while ((hi = apr_hash_first(NULL, co->cacheht)) != NULL) {
  +    while ((hi = cache_hash_first(NULL, co->cacheht)) != NULL) {
           /* Fetch the object from the cache */
  -        apr_hash_this(hi, NULL, NULL, (void **)&obj);
  +        cache_hash_this(hi, NULL, NULL, (void **)&obj);
           if (obj) {
               /* Remove the object from the cache */
  -            apr_hash_set(sconf->cacheht, obj->key, strlen(obj->key), NULL);
  +            cache_hash_set(sconf->cacheht, obj->key, strlen(obj->key), NULL);
               /* Free the object if the recount == 0 */
   #ifdef USE_ATOMICS
               apr_atomic_inc(&obj->refcount);
  @@ -288,12 +282,12 @@
   
       sconf = apr_pcalloc(p, sizeof(mem_cache_conf));
   
  -
       ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm);
       if (threaded_mpm) {
           apr_thread_mutex_create(&sconf->lock, APR_THREAD_MUTEX_DEFAULT, p);
       }
  -    sconf->cacheht = apr_hash_make(p);
  +    /* Todo: determine hash table size from max_cache_object_cnt */
  +    sconf->cacheht = cache_hash_make(512);
   
       sconf->min_cache_object_size = DEFAULT_MIN_CACHE_OBJECT_SIZE;
       sconf->max_cache_object_size = DEFAULT_MAX_CACHE_OBJECT_SIZE;
  @@ -399,11 +393,11 @@
       if (sconf->lock) {
           apr_thread_mutex_lock(sconf->lock);
       }
  -    tmp_obj = (cache_object_t *) apr_hash_get(sconf->cacheht, 
  +    tmp_obj = (cache_object_t *) cache_hash_get(sconf->cacheht, 
                                                 key, 
  -                                              APR_HASH_KEY_STRING);
  +                                              CACHE_HASH_KEY_STRING);
       if (!tmp_obj) {
  -        apr_hash_set(sconf->cacheht, obj->key, strlen(obj->key), obj);
  +        cache_hash_set(sconf->cacheht, obj->key, strlen(obj->key), obj);
           sconf->object_cnt++;
           sconf->cache_size += len;
       }
  @@ -445,8 +439,8 @@
       if (sconf->lock) {
           apr_thread_mutex_lock(sconf->lock);
       }
  -    obj = (cache_object_t *) apr_hash_get(sconf->cacheht, key, 
  -                                          APR_HASH_KEY_STRING);
  +    obj = (cache_object_t *) cache_hash_get(sconf->cacheht, key, 
  +                                          CACHE_HASH_KEY_STRING);
       if (obj) {
           if (obj->complete) {
               request_rec *rmain=r, *rtmp;
  @@ -508,10 +502,11 @@
        */
       if (!obj->cleanup) {
           mem_cache_object_t *mobj = (mem_cache_object_t *) obj->vobj;
  -        apr_hash_set(sconf->cacheht, obj->key, strlen(obj->key), NULL);
  +        cache_hash_set(sconf->cacheht, obj->key, strlen(obj->key), NULL);
           sconf->object_cnt--;
           sconf->cache_size -= mobj->m_len;
           obj->cleanup = 1;
  +        ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, NULL, "gcing a cache entry");
       }
   
       if (sconf->lock) {
  @@ -585,13 +580,6 @@
       if (strcasecmp(type, "mem") && strcasecmp(type, "fd")) {
           return DECLINED;
       }
  -
  -    /* WIBNIF
  -     * apr_hash_set(..,..,..,NULL) returned pointer to the object just removed.
  -     * That way, we could free the object w/o doing another call to an
  -     * apr_hash function.
  -     */
  -
       /* Order of the operations is important to avoid race conditions. 
        * First, remove the object from the cache. Remember, all additions
        * deletions from the cache are protected by sconf->lock.
  @@ -604,11 +592,11 @@
       if (sconf->lock) {
           apr_thread_mutex_lock(sconf->lock);
       }
  -    obj = (cache_object_t *) apr_hash_get(sconf->cacheht, key, 
  -                                          APR_HASH_KEY_STRING);
  +    /* This call will return a pointer to the cache_object just removed */
  +    obj = cache_hash_set(sconf->cacheht, key, CACHE_HASH_KEY_STRING, NULL);
       if (obj) {
  +        /* obj has been removed from the cache */
           mem_cache_object_t *mobj = (mem_cache_object_t *) obj->vobj;
  -        apr_hash_set(sconf->cacheht, key, APR_HASH_KEY_STRING, NULL);
           sconf->object_cnt--;
           sconf->cache_size -= mobj->m_len;
   
  
  
  
  1.7       +10 -2     httpd-2.0/modules/experimental/mod_cache.dsp
  
  Index: mod_cache.dsp
  ===================================================================
  RCS file: /home/cvs/httpd-2.0/modules/experimental/mod_cache.dsp,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- mod_cache.dsp	3 Apr 2002 06:09:18 -0000	1.6
  +++ mod_cache.dsp	4 May 2002 19:54:39 -0000	1.7
  @@ -52,8 +52,8 @@
   # ADD BASE BSC32 /nologo
   # ADD BSC32 /nologo
   LINK32=link.exe
  -# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /map /machine:I386
  -# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /map /machine:I386 /out:"Release/mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache
  +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /map /machine:I386
  +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /map /machine:I386 /out:"Release/mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache
   
   !ELSEIF  "$(CFG)" == "mod_cache - Win32 Debug"
   
  @@ -92,6 +92,10 @@
   # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
   # Begin Source File
   
  +SOURCE=.\cache_hash.c
  +# End Source File
  +# Begin Source File
  +
   SOURCE=.\cache_storage.c
   # End Source File
   # Begin Source File
  @@ -106,6 +110,10 @@
   # Begin Group "Header Files"
   
   # PROP Default_Filter "h;hpp;hxx;hm;inl"
  +# Begin Source File
  +
  +SOURCE=.\cache_hash.h
  +# End Source File
   # Begin Source File
   
   SOURCE=.\mod_cache.h
  
  
  
  1.1                  httpd-2.0/modules/experimental/cache_hash.c
  
  Index: cache_hash.c
  ===================================================================
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000-2002 The Apache Software Foundation.  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
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * 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 APACHE SOFTWARE FOUNDATION 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 Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * Portions of this software are based upon public domain software
   * originally written at the National Center for Supercomputing Applications,
   * University of Illinois, Urbana-Champaign.
   */
  
  #include "apr_general.h"
  
  #include "mod_cache.h"
  #include "cache_hash.h"
  
  #if APR_HAVE_STDLIB_H
  #include <stdlib.h>
  #endif
  #if APR_HAVE_STRING_H
  #include <string.h>
  #endif
  
  
  /*
   * The internal form of a hash table.
   *
   * The table is an array indexed by the hash of the key; collisions
   * are resolved by hanging a linked list of hash entries off each
   * element of the array. Although this is a really simple design it
   * isn't too bad given that pools have a low allocation overhead.
   */
  
  typedef struct cache_hash_entry_t cache_hash_entry_t;
  
  struct cache_hash_entry_t {
      cache_hash_entry_t	*next;
      int			 hash;
      const void		*key;
      apr_ssize_t		 klen;
      const void		*val;
  };
  
  /*
   * Data structure for iterating through a hash table.
   *
   * We keep a pointer to the next hash entry here to allow the current
   * hash entry to be freed or otherwise mangled between calls to
   * cache_hash_next().
   */
  struct cache_hash_index_t {
      cache_hash_t	 *ht;
      cache_hash_entry_t   *this, *next;
      int                  index;
  };
  
  /*
   * The size of the array is always a power of two. We use the maximum
   * index rather than the size so that we can use bitwise-AND for
   * modular arithmetic.
   * The count of hash entries may be greater depending on the chosen
   * collision rate.
   */
  struct cache_hash_t {
      cache_hash_entry_t   **array;
      cache_hash_index_t     iterator;  /* For cache_hash_first(NULL, ...) */
      int                  count, max;
  };
  
  /*
   * Hash creation functions.
   */
  static cache_hash_entry_t **alloc_array(cache_hash_t *ht, int max)
  {
     return calloc(1, sizeof(*ht->array) * (max + 1));
  }
  
  CACHE_DECLARE(cache_hash_t *) cache_hash_make(apr_size_t size)
  {
      cache_hash_t *ht;
      ht = malloc(sizeof(cache_hash_t));
      if (!ht) {
          return NULL;
      }
      ht->count = 0;
      ht->max = size;
      ht->array = alloc_array(ht, ht->max);
      if (!ht->array) {
          free(ht);
          return NULL;
      }
      return ht;
  }
  
  /*
   * Hash iteration functions.
   */
  
  CACHE_DECLARE(cache_hash_index_t *) cache_hash_next(cache_hash_index_t *hi)
  {
      hi->this = hi->next;
      while (!hi->this) {
  	if (hi->index > hi->ht->max)
  	    return NULL;
  	hi->this = hi->ht->array[hi->index++];
      }
      hi->next = hi->this->next;
      return hi;
  }
  
  CACHE_DECLARE(cache_hash_index_t *) cache_hash_first(cache_hash_t *ht)
  {
      cache_hash_index_t *hi;
  
      hi = &ht->iterator;
      hi->ht = ht;
      hi->index = 0;
      hi->this = NULL;
      hi->next = NULL;
      return cache_hash_next(hi);
  }
  
  CACHE_DECLARE(void) cache_hash_this(cache_hash_index_t *hi,
                                    const void **key,
                                    apr_ssize_t *klen,
                                    void **val)
  {
      if (key)  *key  = hi->this->key;
      if (klen) *klen = hi->this->klen;
      if (val)  *val  = (void *)hi->this->val;
  }
  
  
  /*
   * This is where we keep the details of the hash function and control
   * the maximum collision rate.
   *
   * If val is non-NULL it creates and initializes a new hash entry if
   * there isn't already one there; it returns an updatable pointer so
   * that hash entries can be removed.
   */
  
  static cache_hash_entry_t **find_entry(cache_hash_t *ht,
                                         const void *key,
                                         apr_ssize_t klen,
                                         const void *val)
  {
      cache_hash_entry_t **hep, *he;
      const unsigned char *p;
      int hash;
      apr_ssize_t i;
  
      /*
       * This is the popular `times 33' hash algorithm which is used by
       * perl and also appears in Berkeley DB. This is one of the best
       * known hash functions for strings because it is both computed
       * very fast and distributes very well.
       *
       * The originator may be Dan Bernstein but the code in Berkeley DB
       * cites Chris Torek as the source. The best citation I have found
       * is "Chris Torek, Hash function for text in C, Usenet message
       * <27...@mimsy.umd.edu> in comp.lang.c , October, 1990." in Rich
       * Salz's USENIX 1992 paper about INN which can be found at
       * <http://citeseer.nj.nec.com/salz92internetnews.html>.
       *
       * The magic of number 33, i.e. why it works better than many other
       * constants, prime or not, has never been adequately explained by
       * anyone. So I try an explanation: if one experimentally tests all
       * multipliers between 1 and 256 (as I did while writing a low-level
       * data structure library some time ago) one detects that even
       * numbers are not useable at all. The remaining 128 odd numbers
       * (except for the number 1) work more or less all equally well.
       * They all distribute in an acceptable way and this way fill a hash
       * table with an average percent of approx. 86%.
       *
       * If one compares the chi^2 values of the variants (see
       * Bob Jenkins ``Hashing Frequently Asked Questions'' at
       * http://burtleburtle.net/bob/hash/hashfaq.html for a description
       * of chi^2), the number 33 not even has the best value. But the
       * number 33 and a few other equally good numbers like 17, 31, 63,
       * 127 and 129 have nevertheless a great advantage to the remaining
       * numbers in the large set of possible multipliers: their multiply
       * operation can be replaced by a faster operation based on just one
       * shift plus either a single addition or subtraction operation. And
       * because a hash function has to both distribute good _and_ has to
       * be very fast to compute, those few numbers should be preferred.
       *
       *                  -- Ralf S. Engelschall <rs...@engelschall.com>
       */
      hash = 0;
      if (klen == CACHE_HASH_KEY_STRING) {
          for (p = key; *p; p++) {
              hash = hash * 33 + *p;
          }
          klen = p - (const unsigned char *)key;
      }
      else {
          for (p = key, i = klen; i; i--, p++) {
              hash = hash * 33 + *p;
          }
      }
      
      /* scan linked list */
      for (hep = &ht->array[hash & ht->max], he = *hep;
  	 he;
  	 hep = &he->next, he = *hep) {
  	if (he->hash == hash &&
  	    he->klen == klen &&
  	    memcmp(he->key, key, klen) == 0)
  	    break;
      }
      if (he || !val)
  	return hep;
      /* add a new entry for non-NULL values */
      he = malloc(sizeof(*he));
      if (!he) {
          return NULL;
      }
      he->next = NULL;
      he->hash = hash;
      he->key  = key;
      he->klen = klen;
      he->val  = val;
      *hep = he;
      ht->count++;
      return hep;
  }
  
  CACHE_DECLARE(void *) cache_hash_get(cache_hash_t *ht,
                                     const void *key,
                                     apr_ssize_t klen)
  {
      cache_hash_entry_t *he;
      he = *find_entry(ht, key, klen, NULL);
      if (he)
  	return (void *)he->val;
      else
  	return NULL;
  }
  
  CACHE_DECLARE(void*) cache_hash_set(cache_hash_t *ht,
                                    const void *key,
                                    apr_ssize_t klen,
                                    const void *val)
  {
      cache_hash_entry_t **hep, *tmp;
      const void *tval;
      hep = find_entry(ht, key, klen, val);
      if (*hep) {
          if (!val) {
              /* delete entry */
              tval = (*hep)->val;
              tmp = *hep;
              *hep = (*hep)->next;
              free(tmp);
              --ht->count;
          }
          else {
              /* replace entry */
              tval = (*hep)->val;
              (*hep)->val = val;
          }
          /* Return the object just removed from the cache
           * to let the caller clean it up
           */
          return tval;
      }
      /* else key not present and val==NULL */
      return NULL;
  }
  
  CACHE_DECLARE(int) cache_hash_count(cache_hash_t *ht)
  {
      return ht->count;
  }
  
  
  
  1.1                  httpd-2.0/modules/experimental/cache_hash.h
  
  Index: cache_hash.h
  ===================================================================
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000-2002 The Apache Software Foundation.  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
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * 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 APACHE SOFTWARE FOUNDATION 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 Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * Portions of this software are based upon public domain software
   * originally written at the National Center for Supercomputing Applications,
   * University of Illinois, Urbana-Champaign.
   */
  
  #ifndef CACHE_HASH_H
  #define CACHE_HASH_H
  
  #ifdef __cplusplus
  extern "C" {
  #endif
  
  #include "mod_cache.h"
  
  /**
   * @file cache_hash.h
   * @brief Cache Hash Tables
   */
  
  /**
   * @defgroup Cache_Hash  Hash Tables
   * @ingroup CACHE
   * @{
   */
  
  /**
   * When passing a key to cache_hash_set or cache_hash_get, this value can be
   * passed to indicate a string-valued key, and have cache_hash compute the
   * length automatically.
   *
   * @remark cache_hash will use strlen(key) for the length. The null-terminator
   *         is not included in the hash value (why throw a constant in?).
   *         Since the hash table merely references the provided key (rather
   *         than copying it), cache_hash_this() will return the null-term'd key.
   */
  #define CACHE_HASH_KEY_STRING     (-1)
  
  /**
   * Abstract type for hash tables.
   */
  typedef struct cache_hash_t cache_hash_t;
  
  /**
   * Abstract type for scanning hash tables.
   */
  typedef struct cache_hash_index_t cache_hash_index_t;
  
  /**
   * Create a hash table.
   * @param size 
   * @return The hash table just created
    */
  CACHE_DECLARE(cache_hash_t *) cache_hash_make(apr_size_t size);
  
  
  /**
   * Associate a value with a key in a hash table.
   * @param ht The hash table
   * @param key Pointer to the key
   * @param klen Length of the key. Can be CACHE_HASH_KEY_STRING to use the string length.
   * @param val Value to associate with the key
   * @remark If the value is NULL the hash entry is deleted.
   * @return The value of the deleted cache entry (so the caller can clean it up).
   */
  CACHE_DECLARE(void *) cache_hash_set(cache_hash_t *ht, const void *key,
                                     apr_ssize_t klen, const void *val);
  
  /**
   * Look up the value associated with a key in a hash table.
   * @param ht The hash table
   * @param key Pointer to the key
   * @param klen Length of the key. Can be CACHE_HASH_KEY_STRING to use the string length.
   * @return Returns NULL if the key is not present.
   */
  CACHE_DECLARE(void *) cache_hash_get(cache_hash_t *ht, const void *key,
                                     apr_ssize_t klen);
  
  /**
   * Start iterating over the entries in a hash table.
   * @param p The pool to allocate the cache_hash_index_t iterator. If this
   *          pool is NULL, then an internal, non-thread-safe iterator is used.
   * @param ht The hash table
   * @example
   */
  /**
   * <PRE>
   * 
   *     int sum_values(apr_pool_t *p, cache_hash_t *ht)
   *     {
   *         cache_hash_index_t *hi;
   * 	   void *val;
   * 	   int sum = 0;
   * 	   for (hi = cache_hash_first(p, ht); hi; hi = cache_hash_next(hi)) {
   * 	       cache_hash_this(hi, NULL, NULL, &val);
   * 	       sum += *(int *)val;
   * 	   }
   * 	   return sum;
   *     }
   * 
   * There is no restriction on adding or deleting hash entries during an
   * iteration (although the results may be unpredictable unless all you do
   * is delete the current entry) and multiple iterations can be in
   * progress at the same time.
   * </PRE>
    */
  CACHE_DECLARE(cache_hash_index_t *) cache_hash_first(cache_hash_t *ht);
  
  /**
   * Continue iterating over the entries in a hash table.
   * @param hi The iteration state
   * @return a pointer to the updated iteration state.  NULL if there are no more  
   *         entries.
   */
  CACHE_DECLARE(cache_hash_index_t *) cache_hash_next(cache_hash_index_t *hi);
  
  /**
   * Get the current entry's details from the iteration state.
   * @param hi The iteration state
   * @param key Return pointer for the pointer to the key.
   * @param klen Return pointer for the key length.
   * @param val Return pointer for the associated value.
   * @remark The return pointers should point to a variable that will be set to the
   *         corresponding data, or they may be NULL if the data isn't interesting.
   */
  CACHE_DECLARE(void) cache_hash_this(cache_hash_index_t *hi, const void **key, 
                                    apr_ssize_t *klen, void **val);
  
  /**
   * Get the number of key/value pairs in the hash table.
   * @param ht The hash table
   * @return The number of key/value pairs in the hash table.
   */
  CACHE_DECLARE(int) cache_hash_count(cache_hash_t *ht);
  
  
  /** @} */
  #ifdef __cplusplus
  }
  #endif
  
  #endif	/* !CACHE_HASH_H */