diff options
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 87ac96168b0fd..889c65a2c336e 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 */ |