diff options
| author | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 | 
|---|---|---|
| committer | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 | 
| commit | 3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch) | |
| tree | 7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/libsvn_subr/cache-membuffer.c | |
| parent | a55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff) | |
Notes
Diffstat (limited to 'subversion/libsvn_subr/cache-membuffer.c')
| -rw-r--r-- | subversion/libsvn_subr/cache-membuffer.c | 428 | 
1 files changed, 350 insertions, 78 deletions
| diff --git a/subversion/libsvn_subr/cache-membuffer.c b/subversion/libsvn_subr/cache-membuffer.c index 87ac96168b0f..889c65a2c336 100644 --- a/subversion/libsvn_subr/cache-membuffer.c +++ b/subversion/libsvn_subr/cache-membuffer.c @@ -28,12 +28,14 @@  #include "svn_pools.h"  #include "svn_checksum.h"  #include "svn_private_config.h" +#include "svn_hash.h"  #include "svn_string.h"  #include "svn_sorts.h"  /* get the MIN macro */  #include "private/svn_atomic.h"  #include "private/svn_dep_compat.h"  #include "private/svn_mutex.h" +#include "private/svn_subr_private.h"  #include "private/svn_string_private.h"  #include "cache.h" @@ -117,6 +119,12 @@   * key length stored in the entry acts as an additional offset to find the   * actual item.   * + * Most keys are 16 bytes or less.  We use the prefix indexes returned by + * a prefix_pool_t instance to uniquely identify the prefix in that case. + * Then the combination of prefix index and key stored in the fingerprint + * is then unique, too, and can never conflict.  No full key construction, + * storage and comparison is needed in that case. + *   * All access to the cached data needs to be serialized. Because we want   * to scale well despite that bottleneck, we simply segment the cache into   * a number of independent caches (segments). Items will be multiplexed based @@ -193,6 +201,10 @@   * entries with the same entry key. However unlikely, though, two different   * full keys (see full_key_t) may have the same entry key.  That is a   * collision and at most one of them can be stored in the cache at any time. + * + * If the prefix is shared, which implies that the variable key part is no + * longer than 16 bytes, then there is a 1:1 mapping between full key and + * entry key.   */  typedef struct entry_key_t  { @@ -200,24 +212,185 @@ typedef struct entry_key_t    apr_uint64_t fingerprint[2];    /* Length of the full key.  This value is aligned to ITEM_ALIGNMENT to -   * make sure the subsequent item content is properly aligned. */ +   * make sure the subsequent item content is properly aligned.  If 0, +   * PREFIX_KEY is implied to be != NO_INDEX. */    apr_size_t key_len; + +  /* Unique index of the shared key prefix, i.e. it's index within the +   * prefix pool (see prefix_pool_t).  NO_INDEX if the key prefix is not +   * shared, otherwise KEY_LEN==0 is implied. */ +  apr_uint32_t prefix_idx;  } entry_key_t;  /* A full key, i.e. the combination of the cache's key prefix with some   * dynamic part appended to it.  It also contains its ENTRY_KEY. + * + * If the ENTRY_KEY has a 1:1 mapping to the FULL_KEY, then the latter + * will be empty and remains unused.   */  typedef struct full_key_t  {    /* Reduced form identifying the cache entry (if such an entry exists). */    entry_key_t entry_key; -  /* This contains the full combination.  Note that the SIZE element may -   * be larger than ENTRY_KEY.KEY_LEN, but only the latter determines the -   * valid key size. */ +  /* If ENTRY_KEY is not a 1:1 mapping of the prefix + dynamic key +   * combination,  then this contains the full combination.  Note that the +   * SIZE element may be larger than ENTRY_KEY.KEY_LEN, but only the latter +   * determines the valid key size. */    svn_membuf_t full_key;  } full_key_t; +/* A limited capacity, thread-safe pool of unique C strings.  Operations on + * this data structure are defined by prefix_pool_* functions.  The only + * "public" member is VALUES (r/o access only). + */ +typedef struct prefix_pool_t +{ +  /* Map C string to a pointer into VALUES with the same contents. */ +  apr_hash_t *map; + +  /* Pointer to an array of strings. These are the contents of this pool +   * and each one of them is referenced by MAP.  Valid indexes are 0 to +   * VALUES_USED - 1.  May be NULL if VALUES_MAX is 0. */ +  const char **values; + +  /* Number of used entries that VALUES may have. */ +  apr_uint32_t values_max; + +  /* Number of used entries in VALUES.  Never exceeds VALUES_MAX. */ +  apr_uint32_t values_used; + +  /* Maximum number of bytes to allocate. */ +  apr_size_t bytes_max; + +  /* Number of bytes currently allocated.  Should not exceed BYTES_MAX but +   * the implementation may . */ +  apr_size_t bytes_used; + +  /* The serialization object. */ +  svn_mutex__t *mutex; +} prefix_pool_t; + +/* Set *PREFIX_POOL to a new instance that tries to limit allocation to + * BYTES_MAX bytes.  If MUTEX_REQUIRED is set and multi-threading is + * supported, serialize all access to the new instance.  Allocate the + * object from *RESULT_POOL. */ +static svn_error_t * +prefix_pool_create(prefix_pool_t **prefix_pool, +                   apr_size_t bytes_max, +                   svn_boolean_t mutex_required, +                   apr_pool_t *result_pool) +{ +  enum +    { +      /* With 56 byes of overhead under 64 bits, we will probably never get +       * substantially below this.  If we accidentally do, we will simply +       * run out of entries in the VALUES array before running out of +       * allocated memory. */ +      ESTIMATED_BYTES_PER_ENTRY = 120 +    }; + +  /* Number of entries we are going to support. */ +  apr_size_t capacity = MIN(APR_UINT32_MAX, +                            bytes_max / ESTIMATED_BYTES_PER_ENTRY); + +  /* Construct the result struct. */ +  prefix_pool_t *result = apr_pcalloc(result_pool, sizeof(*result)); +  result->map = svn_hash__make(result_pool); + +  result->values = capacity +                 ? apr_pcalloc(result_pool, capacity * sizeof(const char *)) +                 : NULL; +  result->values_max = (apr_uint32_t)capacity; +  result->values_used = 0; + +  result->bytes_max = bytes_max; +  result->bytes_used = capacity * sizeof(svn_membuf_t); + +  SVN_ERR(svn_mutex__init(&result->mutex, mutex_required, result_pool)); + +  /* Done. */ +  *prefix_pool = result; +  return SVN_NO_ERROR; +} + +/* Set *PREFIX_IDX to the offset in PREFIX_POOL->VALUES that contains the + * value PREFIX.  If none exists, auto-insert it.  If we can't due to + * capacity exhaustion, set *PREFIX_IDX to NO_INDEX. + * To be called by prefix_pool_get() only. */ +static svn_error_t * +prefix_pool_get_internal(apr_uint32_t *prefix_idx, +                         prefix_pool_t *prefix_pool, +                         const char *prefix) +{ +  enum +    { +      /* Size of an hash entry plus (max.) APR alignment loss. +       * +       * This may be slightly off if e.g. APR changes its internal data +       * structures but that will translate in just a few percent (~10%) +       * over-allocation.  Memory consumption will still be capped. +       */ +      OVERHEAD = 40 + 8 +    }; + +  const char **value; +  apr_size_t prefix_len = strlen(prefix); +  apr_size_t bytes_needed; +  apr_pool_t *pool; + +  /* Lookup.  If we already know that prefix, return its index. */ +  value = apr_hash_get(prefix_pool->map, prefix, prefix_len); +  if (value != NULL) +    { +      const apr_size_t idx = value - prefix_pool->values; +      SVN_ERR_ASSERT(idx < prefix_pool->values_used); +      *prefix_idx = (apr_uint32_t) idx; +      return SVN_NO_ERROR; +    } + +  /* Capacity checks. */ +  if (prefix_pool->values_used == prefix_pool->values_max) +    { +      *prefix_idx = NO_INDEX; +      return SVN_NO_ERROR; +    } + +  bytes_needed = prefix_len + 1 + OVERHEAD; +  assert(prefix_pool->bytes_max >= prefix_pool->bytes_used); +  if (prefix_pool->bytes_max - prefix_pool->bytes_used < bytes_needed) +    { +      *prefix_idx = NO_INDEX; +      return SVN_NO_ERROR; +    } + +  /* Add new entry. */ +  pool = apr_hash_pool_get(prefix_pool->map); + +  value = &prefix_pool->values[prefix_pool->values_used]; +  *value = apr_pstrndup(pool, prefix, prefix_len + 1); +  apr_hash_set(prefix_pool->map, *value, prefix_len, value); + +  *prefix_idx = prefix_pool->values_used; +  ++prefix_pool->values_used; +  prefix_pool->bytes_used += bytes_needed; + +  return SVN_NO_ERROR; +} + +/* Thread-safe wrapper around prefix_pool_get_internal. */ +static svn_error_t * +prefix_pool_get(apr_uint32_t *prefix_idx, +                prefix_pool_t *prefix_pool, +                const char *prefix) +{ +  SVN_MUTEX__WITH_LOCK(prefix_pool->mutex, +                       prefix_pool_get_internal(prefix_idx, prefix_pool, +                                                prefix)); + +  return SVN_NO_ERROR; +} +  /* Debugging / corruption detection support.   * If you define this macro, the getter functions will performed expensive   * checks on the item data, requested keys and entry types. If there is @@ -267,13 +440,12 @@ typedef struct entry_tag_t  /* Initialize all members of TAG except for the content hash.   */  static svn_error_t *store_key_part(entry_tag_t *tag, -                                   const full_key_t *prefix_key, +                                   const char *prefix,                                     const void *key,                                     apr_size_t key_len, -                                   apr_pool_t *pool) +                                   apr_pool_t *scratch_pool)  {    svn_checksum_t *checksum; -  const char *prefix = prefix_key->full_key.data;    apr_size_t prefix_len = strlen(prefix);    if (prefix_len > sizeof(tag->prefix_tail)) @@ -284,12 +456,16 @@ static svn_error_t *store_key_part(entry_tag_t *tag,    SVN_ERR(svn_checksum(&checksum,                         svn_checksum_md5, +                       prefix, +                       strlen(prefix), +                       scratch_pool)); +  memcpy(tag->prefix_hash, checksum->digest, sizeof(tag->prefix_hash)); + +  SVN_ERR(svn_checksum(&checksum, +                       svn_checksum_md5,                         key,                         key_len, -                       pool)); - -  memcpy(tag->prefix_hash, prefix_key->entry_key.fingerprint, -         sizeof(tag->prefix_hash)); +                       scratch_pool));    memcpy(tag->key_hash, checksum->digest, sizeof(tag->key_hash));    memset(tag->prefix_tail, 0, sizeof(tag->key_hash)); @@ -350,7 +526,7 @@ static svn_error_t* assert_equal_tags(const entry_tag_t *lhs,    entry_tag_t *tag = &_tag;                                      \    if (key)                                                       \      SVN_ERR(store_key_part(tag,                                  \ -                           &cache->prefix,                       \ +                           get_prefix_key(cache),                \                             key,                                  \                             cache->key_len == APR_HASH_KEY_STRING \                                 ? strlen((const char *) key)      \ @@ -525,6 +701,12 @@ struct svn_membuffer_t       and that all segments must / will report the same values here. */    apr_uint32_t segment_count; +  /* Collection of prefixes shared among all instances accessing the +   * same membuffer cache backend.  If a prefix is contained in this +   * pool then all cache instances using an equal prefix must actually +   * use the one stored in this pool. */ +  prefix_pool_t *prefix_pool; +    /* The dictionary, GROUP_SIZE * (group_count + spare_group_count)     * entries long.  Never NULL.     */ @@ -630,6 +812,11 @@ struct svn_membuffer_t     */    svn_boolean_t allow_blocking_writes;  #endif + +  /* A write lock counter, must be either 0 or 1. +   * This one is only used in debug assertions to verify that you used +   * the correct multi-threading settings. */ +  svn_atomic_t write_lock_count;  };  /* Align integer VALUE to the next ITEM_ALIGNMENT boundary. @@ -1186,6 +1373,7 @@ entry_keys_match(const entry_key_t *lhs,  {    return (lhs->fingerprint[0] == rhs->fingerprint[0])        && (lhs->fingerprint[1] == rhs->fingerprint[1]) +      && (lhs->prefix_idx == rhs->prefix_idx)        && (lhs->key_len == rhs->key_len);  } @@ -1248,7 +1436,8 @@ find_entry(svn_membuffer_t *cache,              /* If we want to preserve it, check that it is actual a match. */              if (!find_empty)                { -                /* If there is no full key to compare, we are done. */ +                /* If the full key is fully defined in prefix_id & mangeled +                 * key, we are done. */                  if (!entry->key.key_len)                    return entry; @@ -1347,7 +1536,7 @@ find_entry(svn_membuffer_t *cache,             */            for (i = 0; i < GROUP_SIZE; ++i)              if (entry != &to_shrink->entries[i]) -              let_entry_age(cache, entry); +              let_entry_age(cache, &to_shrink->entries[i]);            drop_entry(cache, entry);          } @@ -1447,7 +1636,7 @@ ensure_data_insertable_l2(svn_membuffer_t *cache,    /* accumulated size of the entries that have been removed to make     * room for the new one.     */ -  apr_size_t moved_size = 0; +  apr_uint64_t moved_size = 0;    /* count the number of entries that got moved.  A single large entry     * being moved is not enough to reject an insertion. @@ -1481,15 +1670,15 @@ ensure_data_insertable_l2(svn_membuffer_t *cache,        /* leave function as soon as the insertion window is large enough         */ -      if (end >= to_fit_in->size + cache->l2.current_data) +      if (end - cache->l2.current_data >= to_fit_in->size)          return TRUE;        /* Don't be too eager to cache data.  If a lot of data has been moved         * around, the current item has probably a relatively low priority. -       * We must also limit the effort spent here (if even in case of faulty +       * We must also limit the effort spent here (even in case of faulty         * heuristics).  Therefore, give up after some time.         */ -      if (moved_size > 4 * to_fit_in->size && moved_count > 7) +      if (moved_size / 4 > to_fit_in->size && moved_count > 7)          return FALSE;        /* if the net worth (in weighted hits) of items removed is already @@ -1606,7 +1795,7 @@ ensure_data_insertable_l1(svn_membuffer_t *cache, apr_size_t size)        /* leave function as soon as the insertion window is large enough         */ -      if (end >= size + cache->l1.current_data) +      if (end - cache->l1.current_data >= size)          return TRUE;        /* Enlarge the insertion window @@ -1653,6 +1842,7 @@ svn_cache__membuffer_cache_create(svn_membuffer_t **cache,                                    apr_pool_t *pool)  {    svn_membuffer_t *c; +  prefix_pool_t *prefix_pool;    apr_uint32_t seg;    apr_uint32_t group_count; @@ -1662,6 +1852,12 @@ svn_cache__membuffer_cache_create(svn_membuffer_t **cache,    apr_uint64_t data_size;    apr_uint64_t max_entry_size; +  /* Allocate 1% of the cache capacity to the prefix string pool. +   */ +  SVN_ERR(prefix_pool_create(&prefix_pool, total_size / 100, thread_safe, +                             pool)); +  total_size -= total_size / 100; +    /* Limit the total size (only relevant if we can address > 4GB)     */  #if APR_SIZEOF_VOIDP > 4 @@ -1751,7 +1947,7 @@ svn_cache__membuffer_cache_create(svn_membuffer_t **cache,                   : data_size / 8;    /* to keep the entries small, we use 32 bit indexes only -   * -> we need to ensure that no more then 4G entries exist. +   * -> we need to ensure that no more than 4G entries exist.     *     * Note, that this limit could only be exceeded in a very     * theoretical setup with about 1EB of cache. @@ -1772,14 +1968,18 @@ svn_cache__membuffer_cache_create(svn_membuffer_t **cache,        /* allocate buffers and initialize cache members         */        c[seg].segment_count = (apr_uint32_t)segment_count; +      c[seg].prefix_pool = prefix_pool;        c[seg].group_count = main_group_count;        c[seg].spare_group_count = spare_group_count;        c[seg].first_spare_group = NO_INDEX;        c[seg].max_spare_used = 0; -      c[seg].directory = apr_pcalloc(pool, -                                     group_count * sizeof(entry_group_t)); +      /* Allocate but don't clear / zero the directory because it would add +         significantly to the server start-up time if the caches are large. +         Group initialization will take care of that in stead. */ +      c[seg].directory = apr_palloc(pool, +                                    group_count * sizeof(entry_group_t));        /* Allocate and initialize directory entries as "not initialized",           hence "unused" */ @@ -1844,6 +2044,8 @@ svn_cache__membuffer_cache_create(svn_membuffer_t **cache,         */        c[seg].allow_blocking_writes = allow_blocking_writes;  #endif +      /* No writers at the moment. */ +      c[seg].write_lock_count = 0;      }    /* done here @@ -1998,14 +2200,28 @@ membuffer_cache_set_internal(svn_membuffer_t *cache,                               apr_pool_t *scratch_pool)  {    cache_level_t *level; -  apr_size_t size = item_size + to_find->entry_key.key_len; +  apr_size_t size;    /* first, look for a previous entry for the given key */    entry_t *entry = find_entry(cache, group_index, to_find, FALSE); +  /* If this one fails, you are using multiple threads but created the +   * membuffer in single-threaded mode. */ +  assert(0 == svn_atomic_inc(&cache->write_lock_count)); + +  /* Quick check make sure arithmetics will work further down the road. */ +  size = item_size + to_find->entry_key.key_len; +  if (size < item_size) +    { +      /* Arithmetic overflow, so combination of serialized ITEM and KEY +       * cannot not fit into the cache.  Setting BUFFER to NULL will cause +       * the removal of any entry if that exists without writing new data. */ +      buffer = NULL; +    } +    /* if there is an old version of that entry and the new data fits into     * the old spot, just re-use that space. */ -  if (entry && ALIGN_VALUE(entry->size) >= size && buffer) +  if (entry && buffer && ALIGN_VALUE(entry->size) >= size)      {        /* Careful! We need to cast SIZE to the full width of CACHE->DATA_USED         * lest we run into trouble with 32 bit underflow *not* treated as a @@ -2032,6 +2248,10 @@ membuffer_cache_set_internal(svn_membuffer_t *cache,                 item_size);        cache->total_writes++; + +      /* Putting the decrement into an assert() to make it disappear +       * in production code. */ +      assert(0 == svn_atomic_dec(&cache->write_lock_count));        return SVN_NO_ERROR;      } @@ -2084,6 +2304,9 @@ membuffer_cache_set_internal(svn_membuffer_t *cache,          drop_entry(cache, entry);      } +  /* Putting the decrement into an assert() to make it disappear +   * in production code. */ +  assert(0 == svn_atomic_dec(&cache->write_lock_count));    return SVN_NO_ERROR;  } @@ -2212,7 +2435,7 @@ membuffer_cache_get_internal(svn_membuffer_t *cache,  /* Look for the *ITEM identified by KEY. If no item has been stored   * for KEY, *ITEM will be NULL. Otherwise, the DESERIALIZER is called - * re-construct the proper object from the serialized data. + * to re-construct the proper object from the serialized data.   * Allocations will be done in POOL.   */  static svn_error_t * @@ -2463,9 +2686,11 @@ membuffer_cache_set_partial_internal(svn_membuffer_t *cache,            if (item_data != orig_data)              {                /* Remove the old entry and try to make space for the new one. +               * Note that the key has already been stored in the past, i.e. +               * it is shorter than the MAX_ENTRY_SIZE.                 */                drop_entry(cache, entry); -              if (   (cache->max_entry_size >= item_size + key_len) +              if (   (cache->max_entry_size - key_len >= item_size)                    && ensure_data_insertable_l1(cache, item_size + key_len))                  {                    /* Write the new entry. @@ -2560,11 +2785,11 @@ typedef struct svn_membuffer_cache_t     */    svn_cache__deserialize_func_t deserializer; -  /* Prepend this byte sequence to any key passed to us. +  /* Prepend this to any key passed to us.     * This makes our keys different from all keys used by svn_membuffer_cache_t     * instances that we don't want to share cached data with.     */ -  full_key_t prefix; +  entry_key_t prefix;    /* length of the keys that will be passed to us through the     * svn_cache_t interface. May be APR_HASH_KEY_STRING. @@ -2583,10 +2808,14 @@ typedef struct svn_membuffer_cache_t    svn_mutex__t *mutex;  } svn_membuffer_cache_t; -/* After an estimated ALLOCATIONS_PER_POOL_CLEAR allocations, we should - * clear the svn_membuffer_cache_t.pool to keep memory consumption in check. - */ -#define ALLOCATIONS_PER_POOL_CLEAR 10 +/* Return the prefix key used by CACHE. */ +static const char * +get_prefix_key(const svn_membuffer_cache_t *cache) +{ +  return (cache->prefix.prefix_idx == NO_INDEX +       ? cache->combined_key.full_key.data +       : cache->membuffer->prefix_pool->values[cache->prefix.prefix_idx]); +}  /* Basically calculate a hash value for KEY of length KEY_LEN, combine it   * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY. @@ -2600,7 +2829,7 @@ combine_long_key(svn_membuffer_cache_t *cache,  {    apr_uint32_t *digest_buffer;    char *key_copy; -  apr_size_t prefix_len = cache->prefix.entry_key.key_len; +  apr_size_t prefix_len = cache->prefix.key_len;    apr_size_t aligned_key_len;    /* handle variable-length keys */ @@ -2624,9 +2853,9 @@ combine_long_key(svn_membuffer_cache_t *cache,    /* Combine with prefix. */    cache->combined_key.entry_key.fingerprint[0] -    ^= cache->prefix.entry_key.fingerprint[0]; +    ^= cache->prefix.fingerprint[0];    cache->combined_key.entry_key.fingerprint[1] -    ^= cache->prefix.entry_key.fingerprint[1]; +    ^= cache->prefix.fingerprint[1];  }  /* Basically calculate a hash value for KEY of length KEY_LEN, combine it @@ -2637,40 +2866,55 @@ combine_key(svn_membuffer_cache_t *cache,              const void *key,              apr_ssize_t key_len)  { -  /* short, fixed-size keys are the most common case */ -  if (key_len != APR_HASH_KEY_STRING && key_len <= 16) -    { -      const apr_size_t prefix_len = cache->prefix.entry_key.key_len; +  /* copy of *key, padded with 0 */ +  apr_uint64_t data[2]; -      /* Copy of *key, padded with 0. -       * We put it just behind the prefix already copied into the COMBINED_KEY. -       * The buffer space has been allocated when the cache was created. */ -      apr_uint64_t *data = (void *)((char *)cache->combined_key.full_key.data +  -                                    prefix_len); -      assert(prefix_len <= cache->combined_key.full_key.size - 16); -      cache->combined_key.entry_key.key_len = prefix_len + 16; +  /* Do we have to compare full keys? */ +  if (cache->prefix.prefix_idx == NO_INDEX) +    { +      combine_long_key(cache, key, key_len); +      return; +    } -      data[0] = 0; +  /* short, fixed-size keys are the most common case */ +  if (key_len == 16) +    { +      memcpy(data, key, 16); +    } +  else if (key_len == 8) +    { +      memcpy(data, key, 8);        data[1] = 0; -      memcpy(data, key, key_len); - -      /* scramble key DATA.  All of this must be reversible to prevent key -       * collisions.  So, we limit ourselves to xor and permutations. */ -      data[1] = (data[1] << 27) | (data[1] >> 37); -      data[1] ^= data[0] & 0xffff; -      data[0] ^= data[1] & APR_UINT64_C(0xffffffffffff0000); - -      /* combine with this cache's namespace */ -      cache->combined_key.entry_key.fingerprint[0] -        = data[0] ^ cache->prefix.entry_key.fingerprint[0]; -      cache->combined_key.entry_key.fingerprint[1] -        = data[1] ^ cache->prefix.entry_key.fingerprint[1];      }    else      { -      /* longer or variably sized keys */ -      combine_long_key(cache, key, key_len); +      assert(key_len != APR_HASH_KEY_STRING && key_len < 16); +      data[0] = 0; +      data[1] = 0; +      memcpy(data, key, key_len);      } + +  /* Scramble key DATA to spread the key space more evenly across the +   * cache segments and entry buckets.  All of this shall be reversible +   * to prevent key collisions.  So, we limit ourselves to xor and +   * permutations. +   * +   * Since the entry key must preserve the full key (prefix and KEY), +   * the scramble must not introduce KEY collisions. +   */ +  data[1] = (data[1] << 27) | (data[1] >> 37); +  data[1] ^= data[0] & 0xffff; +  data[0] ^= data[1] & APR_UINT64_C(0xffffffffffff0000); + +  /* Combine with this cache's prefix.  This is reversible because the +   * prefix is known through to the respective entry_key element.  So, +   * knowing entry_key.prefix_id, we can still reconstruct KEY (and the +   * prefix key). +   */ +  cache->combined_key.entry_key.fingerprint[0] +    = data[0] ^ cache->prefix.fingerprint[0]; +  cache->combined_key.entry_key.fingerprint[1] +    = data[1] ^ cache->prefix.fingerprint[1];  }  /* Implement svn_cache__vtable_t.get (not thread-safe) @@ -2917,7 +3161,7 @@ svn_membuffer_cache_get_info(void *cache_void,    /* cache front-end specific data */ -  info->id = apr_pstrdup(result_pool, cache->prefix.full_key.data); +  info->id = apr_pstrdup(result_pool, get_prefix_key(cache));    /* collect info from shared cache back-end */ @@ -3106,6 +3350,7 @@ svn_cache__create_membuffer_cache(svn_cache__t **cache_p,                                    const char *prefix,                                    apr_uint32_t priority,                                    svn_boolean_t thread_safe, +                                  svn_boolean_t short_lived,                                    apr_pool_t *result_pool,                                    apr_pool_t *scratch_pool)  { @@ -3136,10 +3381,10 @@ svn_cache__create_membuffer_cache(svn_cache__t **cache_p,    prefix_orig_len = strlen(prefix) + 1;    prefix_len = ALIGN_VALUE(prefix_orig_len); -  svn_membuf__create(&cache->prefix.full_key, prefix_len, result_pool); -  memcpy((char *)cache->prefix.full_key.data, prefix, prefix_orig_len); -  memset((char *)cache->prefix.full_key.data + prefix_orig_len, 0, -         prefix_len - prefix_orig_len); +  /* Paranoia check to ensure pointer arithmetics work as expected. */ +  if (prefix_orig_len >= SVN_MAX_OBJECT_SIZE) +    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, +                            _("Prefix too long"));    /* Construct the folded prefix key. */    SVN_ERR(svn_checksum(&checksum, @@ -3147,17 +3392,44 @@ svn_cache__create_membuffer_cache(svn_cache__t **cache_p,                         prefix,                         strlen(prefix),                         scratch_pool)); -  memcpy(cache->prefix.entry_key.fingerprint, checksum->digest, -         sizeof(cache->prefix.entry_key.fingerprint)); -  cache->prefix.entry_key.key_len = prefix_len; - -  /* Initialize the combined key. Pre-allocate some extra room in the full -   * key such that we probably don't need to re-alloc. */ -  cache->combined_key.entry_key = cache->prefix.entry_key; -  svn_membuf__create(&cache->combined_key.full_key, prefix_len + 200, -                     result_pool); -  memcpy(cache->combined_key.full_key.data, cache->prefix.full_key.data, -         prefix_len); +  memcpy(cache->prefix.fingerprint, checksum->digest, +         sizeof(cache->prefix.fingerprint)); +  cache->prefix.key_len = prefix_len; + +  /* Fix-length keys of up to 16 bytes may be handled without storing the +   * full key separately for each item. */ +  if (   (klen != APR_HASH_KEY_STRING) +      && (klen <= sizeof(cache->combined_key.entry_key.fingerprint)) +      && !short_lived) +    SVN_ERR(prefix_pool_get(&cache->prefix.prefix_idx, +                            membuffer->prefix_pool, +                            prefix)); +  else +    cache->prefix.prefix_idx = NO_INDEX; + +  /* If key combining is not guaranteed to produce unique results, we have +   * to handle full keys.  Otherwise, leave it NULL. */ +  if (cache->prefix.prefix_idx == NO_INDEX) +    { +      /* Initialize the combined key. Pre-allocate some extra room in the +       * full key such that we probably don't need to re-alloc. */ +      cache->combined_key.entry_key = cache->prefix; +      svn_membuf__create(&cache->combined_key.full_key, prefix_len + 200, +                         result_pool); +      memcpy((char *)cache->combined_key.full_key.data, prefix, +             prefix_orig_len); +      memset((char *)cache->combined_key.full_key.data + prefix_orig_len, 0, +             prefix_len - prefix_orig_len); +    } +  else +    { +      /* Initialize the combined key.  We will never have the full combined +       * key, so leave it NULL and set its length to 0 to prevent access to +       * it.  Keep the fingerprint 0 as well b/c it will always be set anew +       * by combine_key(). */ +      cache->combined_key.entry_key.prefix_idx = cache->prefix.prefix_idx; +      cache->combined_key.entry_key.key_len = 0; +    }    /* initialize the generic cache wrapper     */ | 
