1 /*
2 * cache-membuffer.c: in-memory caching for Subversion
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 #include <assert.h>
25 #include <apr_md5.h>
26 #include <apr_thread_rwlock.h>
27
28 #include "svn_pools.h"
29 #include "svn_checksum.h"
30 #include "svn_private_config.h"
31 #include "svn_hash.h"
32 #include "svn_string.h"
33 #include "svn_sorts.h" /* get the MIN macro */
34
35 #include "private/svn_atomic.h"
36 #include "private/svn_dep_compat.h"
37 #include "private/svn_mutex.h"
38 #include "private/svn_subr_private.h"
39 #include "private/svn_string_private.h"
40
41 #include "cache.h"
42 #include "fnv1a.h"
43
44 /*
45 * This svn_cache__t implementation actually consists of two parts:
46 * a shared (per-process) singleton membuffer cache instance and shallow
47 * svn_cache__t front-end instances that each use different key spaces.
48 * For data management, they all forward to the singleton membuffer cache.
49 *
50 * A membuffer cache consists of two parts:
51 *
52 * 1. A linear data buffer containing cached items in a serialized
53 * representation, prefixed by their full cache keys. There may be
54 * arbitrary gaps between entries. This buffer is sub-devided into
55 * (currently two) cache levels.
56 *
57 * 2. A directory of cache entries. This is organized similar to CPU
58 * data caches: for every possible key, there is exactly one group
59 * of entries that may contain the header info for an item with
60 * that given key. The result is a GROUP_SIZE+-way associative cache
61 * whose associativity can be dynamically increased.
62 *
63 * Only the start address of these two data parts are given as a native
64 * pointer. All other references are expressed as offsets to these pointers.
65 * With that design, it is relatively easy to share the same data structure
66 * between different processes and / or to persist them on disk. These
67 * out-of-process features have not been implemented, yet.
68 *
69 * Superficially, cache levels are being used as usual: insertion happens
70 * into L1 and evictions will promote items to L2. But their whole point
71 * is a different one. L1 uses a circular buffer, i.e. we have perfect
72 * caching for the last N bytes where N is the size of L1. L2 uses a more
73 * elaborate scheme based on priorities and hit counts as described below.
74 *
75 * The data buffer usage information is implicitly given by the directory
76 * entries. Every USED entry has a reference to the previous and the next
77 * used dictionary entry and this double-linked list is ordered by the
78 * offsets of their item data within the data buffer. So removing data,
79 * for instance, is done simply by unlinking it from the chain, implicitly
80 * marking the entry as well as the data buffer section previously
81 * associated to it as unused. First and last element of that chain are
82 * being referenced from the respective cache level.
83 *
84 * Insertion can occur at only one, sliding position per cache level. It is
85 * marked by its offset in the data buffer and the index of the first used
86 * entry at or behind that position. If this gap is too small to accommodate
87 * the new item (plus its full key), the insertion window is extended as
88 * described below. The new entry will always be inserted at the bottom end
89 * of the window and since the next used entry is known, properly sorted
90 * insertion is possible.
91 *
92 * To make the cache perform robustly in a wide range of usage scenarios,
93 * L2 uses a randomized variant of LFU (see ensure_data_insertable_l2 for
94 * details). Every item holds a read hit counter and there is a global read
95 * hit counter. The more hits an entry has in relation to the average, the
96 * more it is likely to be kept using a rand()-based condition. The test is
97 * applied only to the entry following the insertion window. If it doesn't
98 * get evicted, it is moved to the begin of that window and the window is
99 * moved.
100 *
101 * Moreover, the entry's hits get halved to make that entry more likely to
102 * be removed the next time the sliding insertion / removal window comes by.
103 * As a result, frequently used entries are likely not to be dropped until
104 * they get not used for a while. Also, even a cache thrashing situation
105 * about 50% of the content survives every 50% of the cache being re-written
106 * with new entries. For details on the fine-tuning involved, see the
107 * comments in ensure_data_insertable_l2().
108 *
109 * Due to the randomized mapping of keys to entry groups, some groups may
110 * overflow. In that case, there are spare groups that can be chained to
111 * an already used group to extend it.
112 *
113 * To limit the entry size and management overhead, not the actual item keys
114 * but only their hashed "fingerprint" will be stored. These are reasonably
115 * unique to prevent collisions, so we only need to support up to one entry
116 * per entry key. To guarantee that there are no conflicts, however, we
117 * store the actual full key immediately in front of the serialized item
118 * data. That is, the entry offset actually points to the full key and the
119 * key length stored in the entry acts as an additional offset to find the
120 * actual item.
121 *
122 * Most keys are 16 bytes or less. We use the prefix indexes returned by
123 * a prefix_pool_t instance to uniquely identify the prefix in that case.
124 * Then the combination of prefix index and key stored in the fingerprint
125 * is then unique, too, and can never conflict. No full key construction,
126 * storage and comparison is needed in that case.
127 *
128 * All access to the cached data needs to be serialized. Because we want
129 * to scale well despite that bottleneck, we simply segment the cache into
130 * a number of independent caches (segments). Items will be multiplexed based
131 * on their hash key.
132 */
133
134 /* APR's read-write lock implementation on Windows is horribly inefficient.
135 * Even with very low contention a runtime overhead of 35% percent has been
136 * measured for 'svn-bench null-export' over ra_serf.
137 *
138 * Use a simple mutex on Windows. Because there is one mutex per segment,
139 * large machines should (and usually can) be configured with large caches
140 * such that read contention is kept low. This is basically the situation
141 * we had before 1.8.
142 */
143 #ifdef WIN32
144 # define USE_SIMPLE_MUTEX 1
145 #else
146 # define USE_SIMPLE_MUTEX 0
147 #endif
148
149 /* For more efficient copy operations, let's align all data items properly.
150 * Since we can't portably align pointers, this is rather the item size
151 * granularity which ensures *relative* alignment within the cache - still
152 * giving us decent copy speeds on most machines.
153 *
154 * Must be a power of 2.
155 */
156 #define ITEM_ALIGNMENT 16
157
158 /* By default, don't create cache segments smaller than this value unless
159 * the total cache size itself is smaller.
160 */
161 #define DEFAULT_MIN_SEGMENT_SIZE APR_UINT64_C(0x2000000)
162
163 /* The minimum segment size we will allow for multi-segmented caches
164 */
165 #define MIN_SEGMENT_SIZE APR_UINT64_C(0x10000)
166
167 /* The maximum number of segments allowed. Larger numbers reduce the size
168 * of each segment, in turn reducing the max size of a cachable item.
169 * Also, each segment gets its own lock object. The actual number supported
170 * by the OS may therefore be lower and svn_cache__membuffer_cache_create
171 * may return an error.
172 */
173 #define MAX_SEGMENT_COUNT 0x10000
174
175 /* As of today, APR won't allocate chunks of 4GB or more. So, limit the
176 * segment size to slightly below that.
177 */
178 #define MAX_SEGMENT_SIZE APR_UINT64_C(0xffff0000)
179
180 /* We don't mark the initialization status for every group but initialize
181 * a number of groups at once. That will allow for a very small init flags
182 * vector that is likely to fit into the CPU caches even for fairly large
183 * membuffer caches. For instance, the default of 32 means 8x32 groups per
184 * byte, i.e. 8 flags/byte x 32 groups/flag x 8 entries/group x 40 index
185 * bytes/entry x 8 cache bytes/index byte = 1kB init vector / 640MB cache.
186 */
187 #define GROUP_INIT_GRANULARITY 32
188
189 /* Invalid index reference value. Equivalent to APR_UINT32_T(-1)
190 */
191 #define NO_INDEX APR_UINT32_MAX
192
193 /* To save space in our group structure, we only use 32 bit size values
194 * and, therefore, limit the size of each entry to just below 4GB.
195 * Supporting larger items is not a good idea as the data transfer
196 * to and from the cache would block other threads for a very long time.
197 */
198 #define MAX_ITEM_SIZE ((apr_uint32_t)(0 - ITEM_ALIGNMENT))
199
200 /* We use this structure to identify cache entries. There cannot be two
201 * entries with the same entry key. However unlikely, though, two different
202 * full keys (see full_key_t) may have the same entry key. That is a
203 * collision and at most one of them can be stored in the cache at any time.
204 *
205 * If the prefix is shared, which implies that the variable key part is no
206 * longer than 16 bytes, then there is a 1:1 mapping between full key and
207 * entry key.
208 */
209 typedef struct entry_key_t
210 {
211 /* 16 byte finger print of the full key. */
212 apr_uint64_t fingerprint[2];
213
214 /* Length of the full key. This value is aligned to ITEM_ALIGNMENT to
215 * make sure the subsequent item content is properly aligned. If 0,
216 * PREFIX_KEY is implied to be != NO_INDEX. */
217 apr_size_t key_len;
218
219 /* Unique index of the shared key prefix, i.e. it's index within the
220 * prefix pool (see prefix_pool_t). NO_INDEX if the key prefix is not
221 * shared, otherwise KEY_LEN==0 is implied. */
222 apr_uint32_t prefix_idx;
223 } entry_key_t;
224
225 /* A full key, i.e. the combination of the cache's key prefix with some
226 * dynamic part appended to it. It also contains its ENTRY_KEY.
227 *
228 * If the ENTRY_KEY has a 1:1 mapping to the FULL_KEY, then the latter
229 * will be empty and remains unused.
230 */
231 typedef struct full_key_t
232 {
233 /* Reduced form identifying the cache entry (if such an entry exists). */
234 entry_key_t entry_key;
235
236 /* If ENTRY_KEY is not a 1:1 mapping of the prefix + dynamic key
237 * combination, then this contains the full combination. Note that the
238 * SIZE element may be larger than ENTRY_KEY.KEY_LEN, but only the latter
239 * determines the valid key size. */
240 svn_membuf_t full_key;
241 } full_key_t;
242
243 /* A limited capacity, thread-safe pool of unique C strings. Operations on
244 * this data structure are defined by prefix_pool_* functions. The only
245 * "public" member is VALUES (r/o access only).
246 */
247 typedef struct prefix_pool_t
248 {
249 /* Map C string to a pointer into VALUES with the same contents. */
250 apr_hash_t *map;
251
252 /* Pointer to an array of strings. These are the contents of this pool
253 * and each one of them is referenced by MAP. Valid indexes are 0 to
254 * VALUES_USED - 1. May be NULL if VALUES_MAX is 0. */
255 const char **values;
256
257 /* Number of used entries that VALUES may have. */
258 apr_uint32_t values_max;
259
260 /* Number of used entries in VALUES. Never exceeds VALUES_MAX. */
261 apr_uint32_t values_used;
262
263 /* Maximum number of bytes to allocate. */
264 apr_size_t bytes_max;
265
266 /* Number of bytes currently allocated. Should not exceed BYTES_MAX but
267 * the implementation may . */
268 apr_size_t bytes_used;
269
270 /* The serialization object. */
271 svn_mutex__t *mutex;
272 } prefix_pool_t;
273
274 /* Set *PREFIX_POOL to a new instance that tries to limit allocation to
275 * BYTES_MAX bytes. If MUTEX_REQUIRED is set and multi-threading is
276 * supported, serialize all access to the new instance. Allocate the
277 * object from *RESULT_POOL. */
278 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)279 prefix_pool_create(prefix_pool_t **prefix_pool,
280 apr_size_t bytes_max,
281 svn_boolean_t mutex_required,
282 apr_pool_t *result_pool)
283 {
284 enum
285 {
286 /* With 56 byes of overhead under 64 bits, we will probably never get
287 * substantially below this. If we accidentally do, we will simply
288 * run out of entries in the VALUES array before running out of
289 * allocated memory. */
290 ESTIMATED_BYTES_PER_ENTRY = 120
291 };
292
293 /* Number of entries we are going to support. */
294 apr_size_t capacity = MIN(APR_UINT32_MAX,
295 bytes_max / ESTIMATED_BYTES_PER_ENTRY);
296
297 /* Construct the result struct. */
298 prefix_pool_t *result = apr_pcalloc(result_pool, sizeof(*result));
299 result->map = svn_hash__make(result_pool);
300
301 result->values = capacity
302 ? apr_pcalloc(result_pool, capacity * sizeof(const char *))
303 : NULL;
304 result->values_max = (apr_uint32_t)capacity;
305 result->values_used = 0;
306
307 result->bytes_max = bytes_max;
308 result->bytes_used = capacity * sizeof(svn_membuf_t);
309
310 SVN_ERR(svn_mutex__init(&result->mutex, mutex_required, result_pool));
311
312 /* Done. */
313 *prefix_pool = result;
314 return SVN_NO_ERROR;
315 }
316
317 /* Set *PREFIX_IDX to the offset in PREFIX_POOL->VALUES that contains the
318 * value PREFIX. If none exists, auto-insert it. If we can't due to
319 * capacity exhaustion, set *PREFIX_IDX to NO_INDEX.
320 * To be called by prefix_pool_get() only. */
321 static svn_error_t *
prefix_pool_get_internal(apr_uint32_t * prefix_idx,prefix_pool_t * prefix_pool,const char * prefix)322 prefix_pool_get_internal(apr_uint32_t *prefix_idx,
323 prefix_pool_t *prefix_pool,
324 const char *prefix)
325 {
326 enum
327 {
328 /* Size of an hash entry plus (max.) APR alignment loss.
329 *
330 * This may be slightly off if e.g. APR changes its internal data
331 * structures but that will translate in just a few percent (~10%)
332 * over-allocation. Memory consumption will still be capped.
333 */
334 OVERHEAD = 40 + 8
335 };
336
337 const char **value;
338 apr_size_t prefix_len = strlen(prefix);
339 apr_size_t bytes_needed;
340 apr_pool_t *pool;
341
342 /* Lookup. If we already know that prefix, return its index. */
343 value = apr_hash_get(prefix_pool->map, prefix, prefix_len);
344 if (value != NULL)
345 {
346 const apr_size_t idx = value - prefix_pool->values;
347 SVN_ERR_ASSERT(idx < prefix_pool->values_used);
348 *prefix_idx = (apr_uint32_t) idx;
349 return SVN_NO_ERROR;
350 }
351
352 /* Capacity checks. */
353 if (prefix_pool->values_used == prefix_pool->values_max)
354 {
355 *prefix_idx = NO_INDEX;
356 return SVN_NO_ERROR;
357 }
358
359 bytes_needed = prefix_len + 1 + OVERHEAD;
360 assert(prefix_pool->bytes_max >= prefix_pool->bytes_used);
361 if (prefix_pool->bytes_max - prefix_pool->bytes_used < bytes_needed)
362 {
363 *prefix_idx = NO_INDEX;
364 return SVN_NO_ERROR;
365 }
366
367 /* Add new entry. */
368 pool = apr_hash_pool_get(prefix_pool->map);
369
370 value = &prefix_pool->values[prefix_pool->values_used];
371 *value = apr_pstrndup(pool, prefix, prefix_len + 1);
372 apr_hash_set(prefix_pool->map, *value, prefix_len, value);
373
374 *prefix_idx = prefix_pool->values_used;
375 ++prefix_pool->values_used;
376 prefix_pool->bytes_used += bytes_needed;
377
378 return SVN_NO_ERROR;
379 }
380
381 /* Thread-safe wrapper around prefix_pool_get_internal. */
382 static svn_error_t *
prefix_pool_get(apr_uint32_t * prefix_idx,prefix_pool_t * prefix_pool,const char * prefix)383 prefix_pool_get(apr_uint32_t *prefix_idx,
384 prefix_pool_t *prefix_pool,
385 const char *prefix)
386 {
387 SVN_MUTEX__WITH_LOCK(prefix_pool->mutex,
388 prefix_pool_get_internal(prefix_idx, prefix_pool,
389 prefix));
390
391 return SVN_NO_ERROR;
392 }
393
394 /* Debugging / corruption detection support.
395 * If you define this macro, the getter functions will performed expensive
396 * checks on the item data, requested keys and entry types. If there is
397 * a mismatch found in any of them when being compared with the values
398 * remembered in the setter function, an error will be returned.
399 */
400 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
401
402 /* The prefix passed to svn_cache__create_membuffer_cache() effectively
403 * defines the type of all items stored by that cache instance. We'll take
404 * the last 15 bytes + \0 as plaintext for easy identification by the dev.
405 */
406 #define PREFIX_TAIL_LEN 16
407
408 /* This record will be attached to any cache entry. It tracks item data
409 * (content), key and type as hash values and is the baseline against which
410 * the getters will compare their results to detect inconsistencies.
411 */
412 typedef struct entry_tag_t
413 {
414 /* MD5 checksum over the serialized item data.
415 */
416 unsigned char content_hash[APR_MD5_DIGESTSIZE];
417
418 /* Hash value of the svn_cache_t instance that wrote the item
419 * (i.e. a combination of type and repository)
420 */
421 unsigned char prefix_hash[APR_MD5_DIGESTSIZE];
422
423 /* Note that this only covers the variable part of the key,
424 * i.e. it will be different from the full key hash used for
425 * cache indexing.
426 */
427 unsigned char key_hash[APR_MD5_DIGESTSIZE];
428
429 /* Last letters from of the key in human readable format
430 * (ends with the type identifier, e.g. "DAG")
431 */
432 char prefix_tail[PREFIX_TAIL_LEN];
433
434 /* Length of the variable key part.
435 */
436 apr_size_t key_len;
437
438 } entry_tag_t;
439
440 /* Initialize all members of TAG except for the content hash.
441 */
store_key_part(entry_tag_t * tag,const char * prefix,const void * key,apr_size_t key_len,apr_pool_t * scratch_pool)442 static svn_error_t *store_key_part(entry_tag_t *tag,
443 const char *prefix,
444 const void *key,
445 apr_size_t key_len,
446 apr_pool_t *scratch_pool)
447 {
448 svn_checksum_t *checksum;
449 apr_size_t prefix_len = strlen(prefix);
450
451 if (prefix_len > sizeof(tag->prefix_tail))
452 {
453 prefix += prefix_len - (sizeof(tag->prefix_tail) - 1);
454 prefix_len = sizeof(tag->prefix_tail) - 1;
455 }
456
457 SVN_ERR(svn_checksum(&checksum,
458 svn_checksum_md5,
459 prefix,
460 strlen(prefix),
461 scratch_pool));
462 memcpy(tag->prefix_hash, checksum->digest, sizeof(tag->prefix_hash));
463
464 SVN_ERR(svn_checksum(&checksum,
465 svn_checksum_md5,
466 key,
467 key_len,
468 scratch_pool));
469 memcpy(tag->key_hash, checksum->digest, sizeof(tag->key_hash));
470
471 memset(tag->prefix_tail, 0, sizeof(tag->key_hash));
472 memcpy(tag->prefix_tail, prefix, prefix_len + 1);
473
474 tag->key_len = key_len;
475
476 return SVN_NO_ERROR;
477 }
478
479 /* Initialize the content hash member of TAG.
480 */
store_content_part(entry_tag_t * tag,const void * data,apr_size_t size,apr_pool_t * pool)481 static svn_error_t* store_content_part(entry_tag_t *tag,
482 const void *data,
483 apr_size_t size,
484 apr_pool_t *pool)
485 {
486 svn_checksum_t *checksum;
487 SVN_ERR(svn_checksum(&checksum,
488 svn_checksum_md5,
489 data,
490 size,
491 pool));
492
493 memcpy(tag->content_hash, checksum->digest, sizeof(tag->content_hash));
494
495 return SVN_NO_ERROR;
496 }
497
498 /* Compare two tags and fail with an assertion upon differences.
499 */
assert_equal_tags(const entry_tag_t * lhs,const entry_tag_t * rhs)500 static svn_error_t* assert_equal_tags(const entry_tag_t *lhs,
501 const entry_tag_t *rhs)
502 {
503 SVN_ERR_ASSERT(memcmp(lhs->content_hash, rhs->content_hash,
504 sizeof(lhs->content_hash)) == 0);
505 SVN_ERR_ASSERT(memcmp(lhs->prefix_hash, rhs->prefix_hash,
506 sizeof(lhs->prefix_hash)) == 0);
507 SVN_ERR_ASSERT(memcmp(lhs->key_hash, rhs->key_hash,
508 sizeof(lhs->key_hash)) == 0);
509 SVN_ERR_ASSERT(memcmp(lhs->prefix_tail, rhs->prefix_tail,
510 sizeof(lhs->prefix_tail)) == 0);
511
512 SVN_ERR_ASSERT(lhs->key_len == rhs->key_len);
513
514 return SVN_NO_ERROR;
515 }
516
517 /* Reoccurring code snippets.
518 */
519
520 #define DEBUG_CACHE_MEMBUFFER_TAG_ARG entry_tag_t *tag,
521
522 #define DEBUG_CACHE_MEMBUFFER_TAG tag,
523
524 #define DEBUG_CACHE_MEMBUFFER_INIT_TAG(pool) \
525 entry_tag_t _tag; \
526 entry_tag_t *tag = &_tag; \
527 if (key) \
528 SVN_ERR(store_key_part(tag, \
529 get_prefix_key(cache), \
530 key, \
531 cache->key_len == APR_HASH_KEY_STRING \
532 ? strlen((const char *) key) \
533 : cache->key_len, \
534 pool));
535
536 #else
537
538 /* Don't generate any checks if consistency checks have not been enabled.
539 */
540 #define DEBUG_CACHE_MEMBUFFER_TAG_ARG
541 #define DEBUG_CACHE_MEMBUFFER_TAG
542 #define DEBUG_CACHE_MEMBUFFER_INIT_TAG(pool)
543
544 #endif /* SVN_DEBUG_CACHE_MEMBUFFER */
545
546 /* A single dictionary entry. Since all entries will be allocated once
547 * during cache creation, those entries might be either used or unused.
548 * An entry is used if and only if it is contained in the doubly-linked
549 * list of used entries per cache level.
550 */
551 typedef struct entry_t
552 {
553 /* Identifying the data item. Only valid for used entries.
554 */
555 entry_key_t key;
556
557 /* The offset of the cached item's serialized data within the caches
558 * DATA buffer.
559 */
560 apr_uint64_t offset;
561
562 /* Size of the serialized item data. May be 0. The MAX_ITEM_SIZE macro
563 * above ensures that there will be no overflows.
564 * Only valid for used entries.
565 */
566 apr_size_t size;
567
568 /* Number of (read) hits for this entry. Will be reset upon write.
569 * Only valid for used entries.
570 */
571 svn_atomic_t hit_count;
572
573 /* Reference to the next used entry in the order defined by offset.
574 * NO_INDEX indicates the end of the list; this entry must be referenced
575 * by the caches cache_level_t.last member. NO_INDEX also implies that
576 * the data buffer is not used beyond offset+size.
577 * Only valid for used entries.
578 */
579 apr_uint32_t next;
580
581 /* Reference to the previous used entry in the order defined by offset.
582 * NO_INDEX indicates the end of the list; this entry must be referenced
583 * by the caches cache_level_t.first member.
584 * Only valid for used entries.
585 */
586 apr_uint32_t previous;
587
588 /* Priority of this entry. This entry will not be replaced by lower-
589 * priority items.
590 */
591 apr_uint32_t priority;
592 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
593 /* Remember type, content and key hashes.
594 */
595 entry_tag_t tag;
596 #endif
597 } entry_t;
598
599 /* Group header struct.
600 */
601 typedef struct group_header_t
602 {
603 /* number of entries used [0 .. USED-1] */
604 apr_uint32_t used;
605
606 /* next group in the chain or NO_INDEX for the last.
607 * For recycleable unused spare groups, this points to the next
608 * unused spare group */
609 apr_uint32_t next;
610
611 /* previously group in the chain or NO_INDEX for the first */
612 apr_uint32_t previous;
613
614 /* number of elements in the chain from start to here.
615 * >= 1 for used groups, 0 for unused spare groups */
616 apr_uint32_t chain_length;
617
618 } group_header_t;
619
620 /* The size of the group struct should be a power of two make sure it does
621 * not cross memory page boundaries. Since we already access the cache
622 * randomly, having two page table lookups instead of one is bad.
623 */
624 #define GROUP_BLOCK_SIZE 512
625
626 /* A ~10-way associative cache seems to be a good compromise between
627 * performance (worst-case lookups) and efficiency-loss due to collisions.
628 *
629 * This value may be changed to any positive integer.
630 */
631 #define GROUP_SIZE \
632 ((GROUP_BLOCK_SIZE - sizeof(group_header_t)) / sizeof(entry_t))
633
634 /* Maximum number of groups in a chain, i.e. a cache index group can hold
635 * up to GROUP_SIZE * MAX_GROUP_CHAIN_LENGTH entries.
636 */
637 #define MAX_GROUP_CHAIN_LENGTH 8
638
639 /* We group dictionary entries to make this GROUP-SIZE-way associative.
640 */
641 typedef struct entry_group_t
642 {
643 /* group globals */
644 group_header_t header;
645
646 /* padding and also room for future extensions */
647 char padding[GROUP_BLOCK_SIZE - sizeof(group_header_t)
648 - sizeof(entry_t) * GROUP_SIZE];
649
650 /* the actual entries */
651 entry_t entries[GROUP_SIZE];
652
653 } entry_group_t;
654
655 /* Per-cache level header structure. Instances of this are members of
656 * svn_membuffer_t and will use non-overlapping sections of its DATA buffer.
657 * All offset values are global / absolute to that whole buffer.
658 */
659 typedef struct cache_level_t
660 {
661 /* Reference to the first (defined by the order content in the data
662 * buffer) dictionary entry used by any data item.
663 * NO_INDEX for an empty cache.
664 */
665 apr_uint32_t first;
666
667 /* Reference to the last (defined by the order content in the data
668 * buffer) dictionary entry used by any data item.
669 * NO_INDEX for an empty cache.
670 */
671 apr_uint32_t last;
672
673 /* Reference to the first (defined by the order content in the data
674 * buffer) used dictionary entry behind the insertion position
675 * (current_data). If NO_INDEX, the data buffer is free starting at the
676 * current_data offset.
677 */
678 apr_uint32_t next;
679
680
681 /* First offset in the caches DATA buffer that belongs to this level.
682 */
683 apr_uint64_t start_offset;
684
685 /* Size of data buffer allocated to this level in bytes. Must be > 0.
686 */
687 apr_uint64_t size;
688
689 /* Offset in the data buffer where the next insertion shall occur.
690 */
691 apr_uint64_t current_data;
692
693 } cache_level_t;
694
695 /* The cache header structure.
696 */
697 struct svn_membuffer_t
698 {
699 /* Number of cache segments. Must be a power of 2.
700 Please note that this structure represents only one such segment
701 and that all segments must / will report the same values here. */
702 apr_uint32_t segment_count;
703
704 /* Collection of prefixes shared among all instances accessing the
705 * same membuffer cache backend. If a prefix is contained in this
706 * pool then all cache instances using an equal prefix must actually
707 * use the one stored in this pool. */
708 prefix_pool_t *prefix_pool;
709
710 /* The dictionary, GROUP_SIZE * (group_count + spare_group_count)
711 * entries long. Never NULL.
712 */
713 entry_group_t *directory;
714
715 /* Flag array with group_count / GROUP_INIT_GRANULARITY _bit_ elements.
716 * Allows for efficiently marking groups as "not initialized".
717 */
718 unsigned char *group_initialized;
719
720 /* Size of dictionary in groups. Must be > 0.
721 */
722 apr_uint32_t group_count;
723
724 /* Total number of spare groups.
725 */
726 apr_uint32_t spare_group_count;
727
728 /* First recycleable spare group.
729 */
730 apr_uint32_t first_spare_group;
731
732 /* Maximum number of spare groups ever used. I.e. group index
733 * group_count + max_spare_used is the first unused spare group
734 * if first_spare_group is NO_INDEX.
735 */
736 apr_uint32_t max_spare_used;
737
738 /* Pointer to the data buffer, data_size bytes long. Never NULL.
739 */
740 unsigned char *data;
741
742 /* Total number of data buffer bytes in use.
743 */
744 apr_uint64_t data_used;
745
746 /* Largest entry size that we would accept. For total cache sizes
747 * less than 4TB (sic!), this is determined by the total cache size.
748 */
749 apr_uint64_t max_entry_size;
750
751 /* The cache levels, organized as sub-buffers. Since entries in the
752 * DIRECTORY use offsets in DATA for addressing, a cache lookup does
753 * not need to know the cache level of a specific item. Cache levels
754 * are only used to implement a hybrid insertion / eviction strategy.
755 */
756
757 /* First cache level, i.e. most insertions happen here. Very large
758 * items might get inserted directly into L2. L1 is a strict FIFO
759 * ring buffer that does not care about item priorities. All evicted
760 * items get a chance to be promoted to L2.
761 */
762 cache_level_t l1;
763
764 /* Second cache level, i.e. data evicted from L1 will be added here
765 * if the item is "important" enough or the L2 insertion window is large
766 * enough.
767 */
768 cache_level_t l2;
769
770
771 /* Number of used dictionary entries, i.e. number of cached items.
772 * Purely statistical information that may be used for profiling only.
773 * Updates are not synchronized and values may be nonsensicle on some
774 * platforms.
775 */
776 apr_uint32_t used_entries;
777
778 /* Total number of calls to membuffer_cache_get.
779 * Purely statistical information that may be used for profiling only.
780 * Updates are not synchronized and values may be nonsensicle on some
781 * platforms.
782 */
783 apr_uint64_t total_reads;
784
785 /* Total number of calls to membuffer_cache_set.
786 * Purely statistical information that may be used for profiling only.
787 * Updates are not synchronized and values may be nonsensicle on some
788 * platforms.
789 */
790 apr_uint64_t total_writes;
791
792 /* Total number of hits since the cache's creation.
793 * Purely statistical information that may be used for profiling only.
794 * Updates are not synchronized and values may be nonsensicle on some
795 * platforms.
796 */
797 apr_uint64_t total_hits;
798
799 #if (APR_HAS_THREADS && USE_SIMPLE_MUTEX)
800 /* A lock for intra-process synchronization to the cache, or NULL if
801 * the cache's creator doesn't feel the cache needs to be
802 * thread-safe.
803 */
804 svn_mutex__t *lock;
805 #elif (APR_HAS_THREADS && !USE_SIMPLE_MUTEX)
806 /* Same for read-write lock. */
807 apr_thread_rwlock_t *lock;
808
809 /* If set, write access will wait until they get exclusive access.
810 * Otherwise, they will become no-ops if the segment is currently
811 * read-locked. Only used when LOCK is an r/w lock.
812 */
813 svn_boolean_t allow_blocking_writes;
814 #endif
815
816 /* A write lock counter, must be either 0 or 1.
817 * This one is only used in debug assertions to verify that you used
818 * the correct multi-threading settings. */
819 svn_atomic_t write_lock_count;
820 };
821
822 /* Align integer VALUE to the next ITEM_ALIGNMENT boundary.
823 */
824 #define ALIGN_VALUE(value) (((value) + ITEM_ALIGNMENT-1) & -ITEM_ALIGNMENT)
825
826 /* If locking is supported for CACHE, acquire a read lock for it.
827 */
828 static svn_error_t *
read_lock_cache(svn_membuffer_t * cache)829 read_lock_cache(svn_membuffer_t *cache)
830 {
831 #if (APR_HAS_THREADS && USE_SIMPLE_MUTEX)
832 return svn_mutex__lock(cache->lock);
833 #elif (APR_HAS_THREADS && !USE_SIMPLE_MUTEX)
834 if (cache->lock)
835 {
836 apr_status_t status = apr_thread_rwlock_rdlock(cache->lock);
837 if (status)
838 return svn_error_wrap_apr(status, _("Can't lock cache mutex"));
839 }
840
841 return SVN_NO_ERROR;
842 #else
843 return SVN_NO_ERROR;
844 #endif
845 }
846
847 /* If locking is supported for CACHE, acquire a write lock for it.
848 * Set *SUCCESS to FALSE, if we couldn't acquire the write lock;
849 * leave it untouched otherwise.
850 */
851 static svn_error_t *
write_lock_cache(svn_membuffer_t * cache,svn_boolean_t * success)852 write_lock_cache(svn_membuffer_t *cache, svn_boolean_t *success)
853 {
854 #if (APR_HAS_THREADS && USE_SIMPLE_MUTEX)
855 return svn_mutex__lock(cache->lock);
856 #elif (APR_HAS_THREADS && !USE_SIMPLE_MUTEX)
857 if (cache->lock)
858 {
859 apr_status_t status;
860 if (cache->allow_blocking_writes)
861 {
862 status = apr_thread_rwlock_wrlock(cache->lock);
863 }
864 else
865 {
866 status = apr_thread_rwlock_trywrlock(cache->lock);
867 if (SVN_LOCK_IS_BUSY(status))
868 {
869 *success = FALSE;
870 status = APR_SUCCESS;
871 }
872 }
873
874 if (status)
875 return svn_error_wrap_apr(status,
876 _("Can't write-lock cache mutex"));
877 }
878
879 return SVN_NO_ERROR;
880 #else
881 return SVN_NO_ERROR;
882 #endif
883 }
884
885 /* If locking is supported for CACHE, acquire an unconditional write lock
886 * for it.
887 */
888 static svn_error_t *
force_write_lock_cache(svn_membuffer_t * cache)889 force_write_lock_cache(svn_membuffer_t *cache)
890 {
891 #if (APR_HAS_THREADS && USE_SIMPLE_MUTEX)
892 return svn_mutex__lock(cache->lock);
893 #elif (APR_HAS_THREADS && !USE_SIMPLE_MUTEX)
894 apr_status_t status = apr_thread_rwlock_wrlock(cache->lock);
895 if (status)
896 return svn_error_wrap_apr(status,
897 _("Can't write-lock cache mutex"));
898
899 return SVN_NO_ERROR;
900 #else
901 return SVN_NO_ERROR;
902 #endif
903 }
904
905 /* If locking is supported for CACHE, release the current lock
906 * (read or write). Return ERR upon success.
907 */
908 static svn_error_t *
unlock_cache(svn_membuffer_t * cache,svn_error_t * err)909 unlock_cache(svn_membuffer_t *cache, svn_error_t *err)
910 {
911 #if (APR_HAS_THREADS && USE_SIMPLE_MUTEX)
912 return svn_mutex__unlock(cache->lock, err);
913 #elif (APR_HAS_THREADS && !USE_SIMPLE_MUTEX)
914 if (cache->lock)
915 {
916 apr_status_t status = apr_thread_rwlock_unlock(cache->lock);
917 if (err)
918 return err;
919
920 if (status)
921 return svn_error_wrap_apr(status, _("Can't unlock cache mutex"));
922 }
923
924 return err;
925 #else
926 return err;
927 #endif
928 }
929
930 /* If supported, guard the execution of EXPR with a read lock to CACHE.
931 * The macro has been modeled after SVN_MUTEX__WITH_LOCK.
932 */
933 #define WITH_READ_LOCK(cache, expr) \
934 do { \
935 SVN_ERR(read_lock_cache(cache)); \
936 SVN_ERR(unlock_cache(cache, (expr))); \
937 } while (0)
938
939 /* If supported, guard the execution of EXPR with a write lock to CACHE.
940 * The macro has been modeled after SVN_MUTEX__WITH_LOCK.
941 *
942 * The write lock process is complicated if we don't allow to wait for
943 * the lock: If we didn't get the lock, we may still need to remove an
944 * existing entry for the given key because that content is now stale.
945 * Once we discovered such an entry, we unconditionally do a blocking
946 * wait for the write lock. In case no old content could be found, a
947 * failing lock attempt is simply a no-op and we exit the macro.
948 */
949 #define WITH_WRITE_LOCK(cache, expr) \
950 do { \
951 svn_boolean_t got_lock = TRUE; \
952 SVN_ERR(write_lock_cache(cache, &got_lock)); \
953 if (!got_lock) \
954 { \
955 svn_boolean_t exists; \
956 SVN_ERR(entry_exists(cache, group_index, key, &exists)); \
957 if (exists) \
958 SVN_ERR(force_write_lock_cache(cache)); \
959 else \
960 break; \
961 } \
962 SVN_ERR(unlock_cache(cache, (expr))); \
963 } while (0)
964
965 /* Returns 0 if the entry group identified by GROUP_INDEX in CACHE has not
966 * been initialized, yet. In that case, this group can not data. Otherwise,
967 * a non-zero value is returned.
968 */
969 static APR_INLINE unsigned char
is_group_initialized(svn_membuffer_t * cache,apr_uint32_t group_index)970 is_group_initialized(svn_membuffer_t *cache, apr_uint32_t group_index)
971 {
972 unsigned char flags
973 = cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)];
974 unsigned char bit_mask
975 = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
976
977 return flags & bit_mask;
978 }
979
980 /* Initializes the section of the directory in CACHE that contains
981 * the entry group identified by GROUP_INDEX. */
982 static void
initialize_group(svn_membuffer_t * cache,apr_uint32_t group_index)983 initialize_group(svn_membuffer_t *cache, apr_uint32_t group_index)
984 {
985 unsigned char bit_mask;
986 apr_uint32_t i;
987
988 /* range of groups to initialize due to GROUP_INIT_GRANULARITY */
989 apr_uint32_t first_index =
990 (group_index / GROUP_INIT_GRANULARITY) * GROUP_INIT_GRANULARITY;
991 apr_uint32_t last_index = first_index + GROUP_INIT_GRANULARITY;
992 if (last_index > cache->group_count + cache->spare_group_count)
993 last_index = cache->group_count + cache->spare_group_count;
994
995 for (i = first_index; i < last_index; ++i)
996 {
997 group_header_t *header = &cache->directory[i].header;
998 header->used = 0;
999 header->chain_length = 1;
1000 header->next = NO_INDEX;
1001 header->previous = NO_INDEX;
1002 }
1003
1004 /* set the "initialized" bit for these groups */
1005 bit_mask
1006 = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
1007 cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)]
1008 |= bit_mask;
1009 }
1010
1011 /* Return the next available spare group from CACHE and mark it as used.
1012 * May return NULL.
1013 */
1014 static entry_group_t *
allocate_spare_group(svn_membuffer_t * cache)1015 allocate_spare_group(svn_membuffer_t *cache)
1016 {
1017 entry_group_t *group = NULL;
1018
1019 /* is there some ready-to-use group? */
1020 if (cache->first_spare_group != NO_INDEX)
1021 {
1022 group = &cache->directory[cache->first_spare_group];
1023 cache->first_spare_group = group->header.next;
1024 }
1025
1026 /* any so far untouched spares available? */
1027 else if (cache->max_spare_used < cache->spare_group_count)
1028 {
1029 apr_uint32_t group_index = cache->group_count + cache->max_spare_used;
1030 ++cache->max_spare_used;
1031
1032 if (!is_group_initialized(cache, group_index))
1033 initialize_group(cache, group_index);
1034
1035 group = &cache->directory[group_index];
1036 }
1037
1038 /* spare groups must be empty */
1039 assert(!group || !group->header.used);
1040 return group;
1041 }
1042
1043 /* Mark previously allocated spare group GROUP in CACHE as "unused".
1044 */
1045 static void
free_spare_group(svn_membuffer_t * cache,entry_group_t * group)1046 free_spare_group(svn_membuffer_t *cache,
1047 entry_group_t *group)
1048 {
1049 assert(group->header.used == 0);
1050 assert(group->header.previous != NO_INDEX);
1051 assert(group - cache->directory >= (apr_ssize_t)cache->group_count);
1052
1053 /* unchain */
1054 cache->directory[group->header.previous].header.next = NO_INDEX;
1055 group->header.chain_length = 0;
1056 group->header.previous = NO_INDEX;
1057
1058 /* add to chain of spares */
1059 group->header.next = cache->first_spare_group;
1060 cache->first_spare_group = (apr_uint32_t) (group - cache->directory);
1061 }
1062
1063 /* Follow the group chain from GROUP in CACHE to its end and return the last
1064 * group. May return GROUP.
1065 */
1066 static entry_group_t *
last_group_in_chain(svn_membuffer_t * cache,entry_group_t * group)1067 last_group_in_chain(svn_membuffer_t *cache,
1068 entry_group_t *group)
1069 {
1070 while (group->header.next != NO_INDEX)
1071 group = &cache->directory[group->header.next];
1072
1073 return group;
1074 }
1075
1076 /* Return the CHAIN_INDEX-th element in the group chain starting from group
1077 * START_GROUP_INDEX in CACHE.
1078 */
1079 static entry_group_t *
get_group(svn_membuffer_t * cache,apr_uint32_t start_group_index,apr_uint32_t chain_index)1080 get_group(svn_membuffer_t *cache,
1081 apr_uint32_t start_group_index,
1082 apr_uint32_t chain_index)
1083 {
1084 entry_group_t *group = &cache->directory[start_group_index];
1085 for (; chain_index; --chain_index)
1086 group = &cache->directory[group->header.next];
1087
1088 return group;
1089 }
1090
1091 /* Resolve a dictionary entry reference, i.e. return the entry
1092 * for the given IDX.
1093 */
1094 static APR_INLINE entry_t *
get_entry(svn_membuffer_t * cache,apr_uint32_t idx)1095 get_entry(svn_membuffer_t *cache, apr_uint32_t idx)
1096 {
1097 return &cache->directory[idx / GROUP_SIZE].entries[idx % GROUP_SIZE];
1098 }
1099
1100 /* Get the entry references for the given ENTRY.
1101 */
1102 static APR_INLINE apr_uint32_t
get_index(svn_membuffer_t * cache,entry_t * entry)1103 get_index(svn_membuffer_t *cache, entry_t *entry)
1104 {
1105 apr_size_t group_index
1106 = ((char *)entry - (char *)cache->directory) / sizeof(entry_group_t);
1107
1108 return (apr_uint32_t)group_index * GROUP_SIZE
1109 + (apr_uint32_t)(entry - cache->directory[group_index].entries);
1110 }
1111
1112 /* Return the cache level of ENTRY in CACHE.
1113 */
1114 static cache_level_t *
get_cache_level(svn_membuffer_t * cache,entry_t * entry)1115 get_cache_level(svn_membuffer_t *cache, entry_t *entry)
1116 {
1117 return entry->offset < cache->l1.size ? &cache->l1
1118 : &cache->l2;
1119 }
1120
1121 /* Insert ENTRY to the chain of items that belong to LEVEL in CACHE. IDX
1122 * is ENTRY's item index and is only given for efficiency. The insertion
1123 * takes place just before LEVEL->NEXT. *CACHE will not be modified.
1124 */
1125 static void
chain_entry(svn_membuffer_t * cache,cache_level_t * level,entry_t * entry,apr_uint32_t idx)1126 chain_entry(svn_membuffer_t *cache,
1127 cache_level_t *level,
1128 entry_t *entry,
1129 apr_uint32_t idx)
1130 {
1131 /* insert ENTRY before this item */
1132 entry_t *next = level->next == NO_INDEX
1133 ? NULL
1134 : get_entry(cache, level->next);
1135 assert(idx == get_index(cache, entry));
1136
1137 /* update entry chain
1138 */
1139 entry->next = level->next;
1140 if (level->first == NO_INDEX)
1141 {
1142 /* insert as the first entry and only in the chain
1143 */
1144 entry->previous = NO_INDEX;
1145 level->last = idx;
1146 level->first = idx;
1147 }
1148 else if (next == NULL)
1149 {
1150 /* insert as the last entry in the chain.
1151 * Note that it cannot also be at the beginning of the chain.
1152 */
1153 entry->previous = level->last;
1154 get_entry(cache, level->last)->next = idx;
1155 level->last = idx;
1156 }
1157 else
1158 {
1159 /* insert either at the start of a non-empty list or
1160 * somewhere in the middle
1161 */
1162 entry->previous = next->previous;
1163 next->previous = idx;
1164
1165 if (entry->previous != NO_INDEX)
1166 get_entry(cache, entry->previous)->next = idx;
1167 else
1168 level->first = idx;
1169 }
1170 }
1171
1172 /* Remove ENTRY from the chain of items that belong to LEVEL in CACHE. IDX
1173 * is ENTRY's item index and is only given for efficiency. Please note
1174 * that neither *CACHE nor *ENTRY will not be modified.
1175 */
1176 static void
unchain_entry(svn_membuffer_t * cache,cache_level_t * level,entry_t * entry,apr_uint32_t idx)1177 unchain_entry(svn_membuffer_t *cache,
1178 cache_level_t *level,
1179 entry_t *entry,
1180 apr_uint32_t idx)
1181 {
1182 assert(idx == get_index(cache, entry));
1183
1184 /* update
1185 */
1186 if (level->next == idx)
1187 level->next = entry->next;
1188
1189 /* unlink it from the chain of used entries
1190 */
1191 if (entry->previous == NO_INDEX)
1192 level->first = entry->next;
1193 else
1194 get_entry(cache, entry->previous)->next = entry->next;
1195
1196 if (entry->next == NO_INDEX)
1197 level->last = entry->previous;
1198 else
1199 get_entry(cache, entry->next)->previous = entry->previous;
1200 }
1201
1202 /* Remove the used ENTRY from the CACHE, i.e. make it "unused".
1203 * In contrast to insertion, removal is possible for any entry.
1204 */
1205 static void
drop_entry(svn_membuffer_t * cache,entry_t * entry)1206 drop_entry(svn_membuffer_t *cache, entry_t *entry)
1207 {
1208 /* the group that ENTRY belongs to plus a number of useful index values
1209 */
1210 apr_uint32_t idx = get_index(cache, entry);
1211 apr_uint32_t group_index = idx / GROUP_SIZE;
1212 entry_group_t *last_group
1213 = last_group_in_chain(cache, &cache->directory[group_index]);
1214 apr_uint32_t last_in_group
1215 = (apr_uint32_t) ((last_group - cache->directory) * GROUP_SIZE
1216 + last_group->header.used - 1);
1217
1218 cache_level_t *level = get_cache_level(cache, entry);
1219
1220 /* update global cache usage counters
1221 */
1222 cache->used_entries--;
1223 cache->data_used -= entry->size;
1224
1225 /* extend the insertion window, if the entry happens to border it
1226 */
1227 if (idx == level->next)
1228 level->next = entry->next;
1229 else
1230 if (entry->next == level->next)
1231 {
1232 /* insertion window starts right behind the entry to remove
1233 */
1234 if (entry->previous == NO_INDEX)
1235 {
1236 /* remove the first entry -> insertion may start at pos 0, now */
1237 level->current_data = level->start_offset;
1238 }
1239 else
1240 {
1241 /* insertion may start right behind the previous entry */
1242 entry_t *previous = get_entry(cache, entry->previous);
1243 level->current_data = ALIGN_VALUE( previous->offset
1244 + previous->size);
1245 }
1246 }
1247
1248 /* unlink it from the chain of used entries
1249 */
1250 unchain_entry(cache, level, entry, idx);
1251
1252 /* Move last entry into hole (if the removed one is not the last used).
1253 * We need to do this since all used entries are at the beginning of
1254 * the group's entries array.
1255 */
1256 if (idx != last_in_group)
1257 {
1258 /* copy the last used entry to the removed entry's index
1259 */
1260 *entry = last_group->entries[last_group->header.used-1];
1261
1262 /* this ENTRY may belong to a different cache level than the entry
1263 * we have just removed */
1264 level = get_cache_level(cache, entry);
1265
1266 /* update foreign links to new index
1267 */
1268 if (last_in_group == level->next)
1269 level->next = idx;
1270
1271 if (entry->previous == NO_INDEX)
1272 level->first = idx;
1273 else
1274 get_entry(cache, entry->previous)->next = idx;
1275
1276 if (entry->next == NO_INDEX)
1277 level->last = idx;
1278 else
1279 get_entry(cache, entry->next)->previous = idx;
1280 }
1281
1282 /* Update the number of used entries.
1283 */
1284 last_group->header.used--;
1285
1286 /* Release the last group in the chain if it is a spare group
1287 */
1288 if (!last_group->header.used && last_group->header.previous != NO_INDEX)
1289 free_spare_group(cache, last_group);
1290 }
1291
1292 /* Insert ENTRY into the chain of used dictionary entries. The entry's
1293 * offset and size members must already have been initialized. Also,
1294 * the offset must match the beginning of the insertion window.
1295 */
1296 static void
insert_entry(svn_membuffer_t * cache,entry_t * entry)1297 insert_entry(svn_membuffer_t *cache, entry_t *entry)
1298 {
1299 /* the group that ENTRY belongs to plus a number of useful index values
1300 */
1301 apr_uint32_t idx = get_index(cache, entry);
1302 apr_uint32_t group_index = idx / GROUP_SIZE;
1303 entry_group_t *group = &cache->directory[group_index];
1304 cache_level_t *level = get_cache_level(cache, entry);
1305
1306 /* The entry must start at the beginning of the insertion window.
1307 * It must also be the first unused entry in the group.
1308 */
1309 assert(entry->offset == level->current_data);
1310 assert(idx == group_index * GROUP_SIZE + group->header.used);
1311 level->current_data = ALIGN_VALUE(entry->offset + entry->size);
1312
1313 /* update usage counters
1314 */
1315 cache->used_entries++;
1316 cache->data_used += entry->size;
1317 entry->hit_count = 0;
1318 group->header.used++;
1319
1320 /* update entry chain
1321 */
1322 chain_entry(cache, level, entry, idx);
1323
1324 /* The current insertion position must never point outside our
1325 * data buffer.
1326 */
1327 assert(level->current_data <= level->start_offset + level->size);
1328 }
1329
1330 /* Map a KEY of 16 bytes to the CACHE and group that shall contain the
1331 * respective item.
1332 */
1333 static apr_uint32_t
get_group_index(svn_membuffer_t ** cache,const entry_key_t * key)1334 get_group_index(svn_membuffer_t **cache,
1335 const entry_key_t *key)
1336 {
1337 svn_membuffer_t *segment0 = *cache;
1338 apr_uint64_t key0 = key->fingerprint[0];
1339 apr_uint64_t key1 = key->fingerprint[1];
1340
1341 /* select the cache segment to use. they have all the same group_count.
1342 * Since key may not be well-distributed, pre-fold it to a smaller but
1343 * "denser" ranger. The modulus is a prime larger than the largest
1344 * counts. */
1345 *cache = &segment0[(key1 % APR_UINT64_C(2809637) + (key0 / 37))
1346 & (segment0->segment_count - 1)];
1347 return (key0 % APR_UINT64_C(5030895599)) % segment0->group_count;
1348 }
1349
1350 /* Reduce the hit count of ENTRY and update the accumulated hit info
1351 * in CACHE accordingly.
1352 */
1353 static APR_INLINE void
let_entry_age(svn_membuffer_t * cache,entry_t * entry)1354 let_entry_age(svn_membuffer_t *cache, entry_t *entry)
1355 {
1356 apr_uint32_t hits_removed = (entry->hit_count + 1) >> 1;
1357
1358 if (hits_removed)
1359 {
1360 entry->hit_count -= hits_removed;
1361 }
1362 else
1363 {
1364 entry->priority /= 2;
1365 }
1366 }
1367
1368 /* Return whether the keys in LHS and RHS match.
1369 */
1370 static svn_boolean_t
entry_keys_match(const entry_key_t * lhs,const entry_key_t * rhs)1371 entry_keys_match(const entry_key_t *lhs,
1372 const entry_key_t *rhs)
1373 {
1374 return (lhs->fingerprint[0] == rhs->fingerprint[0])
1375 && (lhs->fingerprint[1] == rhs->fingerprint[1])
1376 && (lhs->prefix_idx == rhs->prefix_idx)
1377 && (lhs->key_len == rhs->key_len);
1378 }
1379
1380 /* Given the GROUP_INDEX that shall contain an entry with the hash key
1381 * TO_FIND, find that entry in the specified group.
1382 *
1383 * If FIND_EMPTY is not set, this function will return the one used entry
1384 * that actually matches the hash or NULL, if no such entry exists.
1385 *
1386 * If FIND_EMPTY has been set, this function will drop the one used entry
1387 * that actually matches the hash (i.e. make it fit to be replaced with
1388 * new content), an unused entry or a forcibly removed entry (if all
1389 * group entries are currently in use). The entries' hash value will be
1390 * initialized with TO_FIND.
1391 *
1392 * Note: This function requires the caller to appropriately lock the CACHE.
1393 * For FIND_EMPTY==FALSE, a read lock is required, for FIND_EMPTY==TRUE,
1394 * the write lock must have been acquired.
1395 */
1396 static entry_t *
find_entry(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,svn_boolean_t find_empty)1397 find_entry(svn_membuffer_t *cache,
1398 apr_uint32_t group_index,
1399 const full_key_t *to_find,
1400 svn_boolean_t find_empty)
1401 {
1402 entry_group_t *group;
1403 entry_t *entry = NULL;
1404 apr_size_t i;
1405
1406 /* get the group that *must* contain the entry
1407 */
1408 group = &cache->directory[group_index];
1409
1410 /* If the entry group has not been initialized, yet, there is no data.
1411 */
1412 if (! is_group_initialized(cache, group_index))
1413 {
1414 if (find_empty)
1415 {
1416 initialize_group(cache, group_index);
1417 entry = &group->entries[0];
1418
1419 /* initialize entry for the new key */
1420 entry->key = to_find->entry_key;
1421 }
1422
1423 return entry;
1424 }
1425
1426 /* try to find the matching entry
1427 */
1428 while (1)
1429 {
1430 for (i = 0; i < group->header.used; ++i)
1431 if (entry_keys_match(&group->entries[i].key, &to_find->entry_key))
1432 {
1433 /* This is the only entry that _may_ contain the correct data. */
1434 entry = &group->entries[i];
1435
1436 /* If we want to preserve it, check that it is actual a match. */
1437 if (!find_empty)
1438 {
1439 /* If the full key is fully defined in prefix_id & mangeled
1440 * key, we are done. */
1441 if (!entry->key.key_len)
1442 return entry;
1443
1444 /* Compare the full key. */
1445 if (memcmp(to_find->full_key.data,
1446 cache->data + entry->offset,
1447 entry->key.key_len) == 0)
1448 return entry;
1449
1450 /* Key conflict. The entry to find cannot be anywhere else.
1451 * Therefore, it is not cached. */
1452 return NULL;
1453 }
1454
1455 /* need to empty that entry */
1456 drop_entry(cache, entry);
1457 if (group->header.used == GROUP_SIZE)
1458 group = last_group_in_chain(cache, group);
1459 else if (group->header.chain_length == 0)
1460 group = last_group_in_chain(cache,
1461 &cache->directory[group_index]);
1462
1463 /* No entry found (actually, none left to find). */
1464 entry = NULL;
1465 break;
1466 }
1467
1468 /* end of chain? */
1469 if (group->header.next == NO_INDEX)
1470 break;
1471
1472 /* only full groups may chain */
1473 assert(group->header.used == GROUP_SIZE);
1474 group = &cache->directory[group->header.next];
1475 }
1476
1477 /* None found. Are we looking for a free entry?
1478 */
1479 if (find_empty)
1480 {
1481 /* There is no empty entry in the chain, try chaining a spare group.
1482 */
1483 if ( group->header.used == GROUP_SIZE
1484 && group->header.chain_length < MAX_GROUP_CHAIN_LENGTH)
1485 {
1486 entry_group_t *new_group = allocate_spare_group(cache);
1487 if (new_group)
1488 {
1489 /* chain groups
1490 */
1491 new_group->header.chain_length = group->header.chain_length + 1;
1492 new_group->header.previous = (apr_uint32_t) (group -
1493 cache->directory);
1494 new_group->header.next = NO_INDEX;
1495 group->header.next = (apr_uint32_t) (new_group -
1496 cache->directory);
1497 group = new_group;
1498 }
1499 }
1500
1501 /* if GROUP is still filled, we need to remove a random entry */
1502 if (group->header.used == GROUP_SIZE)
1503 {
1504 /* every entry gets the same chance of being removed.
1505 * Otherwise, we free the first entry, fill it and
1506 * remove it again on the next occasion without considering
1507 * the other entries in this group.
1508 *
1509 * We hit only one random group instead of processing all
1510 * groups in the chain.
1511 */
1512 cache_level_t *entry_level;
1513 int to_remove = rand() % (GROUP_SIZE * group->header.chain_length);
1514 entry_group_t *to_shrink
1515 = get_group(cache, group_index, to_remove / GROUP_SIZE);
1516
1517 entry = &to_shrink->entries[to_remove % GROUP_SIZE];
1518 entry_level = get_cache_level(cache, entry);
1519 for (i = 0; i < GROUP_SIZE; ++i)
1520 {
1521 /* keep L1 entries whenever possible */
1522
1523 cache_level_t *level
1524 = get_cache_level(cache, &to_shrink->entries[i]);
1525 if ( (level != entry_level && entry_level == &cache->l1)
1526 || (entry->hit_count > to_shrink->entries[i].hit_count))
1527 {
1528 entry_level = level;
1529 entry = &to_shrink->entries[i];
1530 }
1531 }
1532
1533 /* for the entries that don't have been removed,
1534 * reduce their hit counts to put them at a relative
1535 * disadvantage the next time.
1536 */
1537 for (i = 0; i < GROUP_SIZE; ++i)
1538 if (entry != &to_shrink->entries[i])
1539 let_entry_age(cache, &to_shrink->entries[i]);
1540
1541 drop_entry(cache, entry);
1542 }
1543
1544 /* initialize entry for the new key
1545 */
1546 entry = &group->entries[group->header.used];
1547 entry->key = to_find->entry_key;
1548 }
1549
1550 return entry;
1551 }
1552
1553 /* Move a surviving ENTRY from just behind the insertion window to
1554 * its beginning and move the insertion window up accordingly.
1555 */
1556 static void
move_entry(svn_membuffer_t * cache,entry_t * entry)1557 move_entry(svn_membuffer_t *cache, entry_t *entry)
1558 {
1559 apr_size_t size = ALIGN_VALUE(entry->size);
1560 cache_level_t *level = get_cache_level(cache, entry);
1561
1562 /* This entry survived this cleansing run. Reset half of its
1563 * hit count so that its removal gets more likely in the next
1564 * run unless someone read / hit this entry in the meantime.
1565 */
1566 let_entry_age(cache, entry);
1567
1568 /* Move the entry to the start of the empty / insertion section
1569 * (if it isn't there already). Size-aligned moves are legal
1570 * since all offsets and block sizes share this same alignment.
1571 * Size-aligned moves tend to be faster than non-aligned ones
1572 * because no "odd" bytes at the end need to special treatment.
1573 */
1574 if (entry->offset != level->current_data)
1575 {
1576 memmove(cache->data + level->current_data,
1577 cache->data + entry->offset,
1578 size);
1579 entry->offset = level->current_data;
1580 }
1581
1582 /* The insertion position is now directly behind this entry.
1583 */
1584 level->current_data = entry->offset + size;
1585 level->next = entry->next;
1586
1587 /* The current insertion position must never point outside our
1588 * data buffer.
1589 */
1590 assert(level->current_data <= level->start_offset + level->size);
1591 }
1592
1593 /* Move ENTRY in CACHE from L1 to L2.
1594 */
1595 static void
promote_entry(svn_membuffer_t * cache,entry_t * entry)1596 promote_entry(svn_membuffer_t *cache, entry_t *entry)
1597 {
1598 apr_uint32_t idx = get_index(cache, entry);
1599 apr_size_t size = ALIGN_VALUE(entry->size);
1600 assert(get_cache_level(cache, entry) == &cache->l1);
1601 assert(idx == cache->l1.next);
1602
1603 /* copy item from the current location in L1 to the start of L2's
1604 * insertion window */
1605 memmove(cache->data + cache->l2.current_data,
1606 cache->data + entry->offset,
1607 size);
1608 entry->offset = cache->l2.current_data;
1609
1610 /* The insertion position is now directly behind this entry.
1611 */
1612 cache->l2.current_data += size;
1613
1614 /* remove ENTRY from chain of L1 entries and put it into L2
1615 */
1616 unchain_entry(cache, &cache->l1, entry, idx);
1617 chain_entry(cache, &cache->l2, entry, idx);
1618 }
1619
1620 /* This function implements the cache insertion / eviction strategy for L2.
1621 *
1622 * If necessary, enlarge the insertion window of CACHE->L2 until it is at
1623 * least TO_FIT_IN->SIZE bytes long. TO_FIT_IN->SIZE must not exceed the
1624 * data buffer size allocated to CACHE->L2. IDX is the item index of
1625 * TO_FIT_IN and is given for performance reasons.
1626 *
1627 * Return TRUE if enough room could be found or made. A FALSE result
1628 * indicates that the respective item shall not be added.
1629 */
1630 static svn_boolean_t
ensure_data_insertable_l2(svn_membuffer_t * cache,entry_t * to_fit_in)1631 ensure_data_insertable_l2(svn_membuffer_t *cache,
1632 entry_t *to_fit_in)
1633 {
1634 entry_t *entry;
1635
1636 /* accumulated size of the entries that have been removed to make
1637 * room for the new one.
1638 */
1639 apr_uint64_t moved_size = 0;
1640
1641 /* count the number of entries that got moved. A single large entry
1642 * being moved is not enough to reject an insertion.
1643 */
1644 apr_size_t moved_count = 0;
1645
1646 /* accumulated "worth" of items dropped so far */
1647 apr_uint64_t drop_hits = 0;
1648
1649 /* estimated "worth" of the new entry */
1650 apr_uint64_t drop_hits_limit = (to_fit_in->hit_count + 1)
1651 * (apr_uint64_t)to_fit_in->priority;
1652
1653 /* This loop will eventually terminate because every cache entry
1654 * would get dropped eventually:
1655 *
1656 * - the incoming entry is small enough to fit into L2
1657 * - every iteration either frees parts of L2 or counts the moved size
1658 * - eventually, we either moved too many items with too much total size
1659 * to accept the new entry, or made enough room in L2 for the new entry
1660 *
1661 * Low-prio items get rejected even sooner.
1662 */
1663 while (1)
1664 {
1665 /* first offset behind the insertion window
1666 */
1667 apr_uint64_t end = cache->l2.next == NO_INDEX
1668 ? cache->l2.start_offset + cache->l2.size
1669 : get_entry(cache, cache->l2.next)->offset;
1670
1671 /* leave function as soon as the insertion window is large enough
1672 */
1673 if (end - cache->l2.current_data >= to_fit_in->size)
1674 return TRUE;
1675
1676 /* Don't be too eager to cache data. If a lot of data has been moved
1677 * around, the current item has probably a relatively low priority.
1678 * We must also limit the effort spent here (even in case of faulty
1679 * heuristics). Therefore, give up after some time.
1680 */
1681 if (moved_size / 4 > to_fit_in->size && moved_count > 7)
1682 return FALSE;
1683
1684 /* if the net worth (in weighted hits) of items removed is already
1685 * larger than what we want to insert, reject TO_FIT_IN because it
1686 * still does not fit in. */
1687 if (drop_hits > drop_hits_limit)
1688 return FALSE;
1689
1690 /* try to enlarge the insertion window
1691 */
1692 if (cache->l2.next == NO_INDEX)
1693 {
1694 /* We reached the end of the data buffer; restart at the beginning.
1695 * Due to the randomized nature of our LFU implementation, very
1696 * large data items may require multiple passes. Therefore, SIZE
1697 * should be restricted to significantly less than data_size.
1698 */
1699 cache->l2.current_data = cache->l2.start_offset;
1700 cache->l2.next = cache->l2.first;
1701 }
1702 else
1703 {
1704 svn_boolean_t keep;
1705 entry = get_entry(cache, cache->l2.next);
1706
1707 if (to_fit_in->priority < SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY)
1708 {
1709 /* Low prio items can only be accepted only if the current
1710 * entry is of even lower prio and has fewer hits.
1711 */
1712 if ( entry->priority > to_fit_in->priority
1713 || entry->hit_count > to_fit_in->hit_count)
1714 return FALSE;
1715 }
1716
1717 if (entry->priority <= SVN_CACHE__MEMBUFFER_LOW_PRIORITY)
1718 {
1719 /* Be quick to remove low-prio entries - even if the incoming
1720 * one is low-prio as well. This makes room for more important
1721 * data and replaces existing data with newly read information.
1722 */
1723 keep = FALSE;
1724 }
1725 else
1726 {
1727 /* If the existing data is the same prio as the incoming data,
1728 * drop the existing entry if it had seen fewer (probably 0)
1729 * hits than the entry coming in from L1. In case of different
1730 * priorities, keep the current entry of it has higher prio.
1731 * The new entry may still find room by ousting other entries.
1732 */
1733 keep = to_fit_in->priority == entry->priority
1734 ? entry->hit_count >= to_fit_in->hit_count
1735 : entry->priority > to_fit_in->priority;
1736 }
1737
1738 /* keepers or destroyers? */
1739 if (keep)
1740 {
1741 /* Moving entries around is not for free -> track costs. */
1742 moved_size += entry->size;
1743 moved_count++;
1744
1745 move_entry(cache, entry);
1746 }
1747 else
1748 {
1749 /* Drop the entry from the end of the insertion window.
1750 * Count the "hit importance" such that we are not sacrificing
1751 * too much of the high-hit contents. However, don't count
1752 * low-priority hits because higher prio entries will often
1753 * provide the same data but in a further stage of processing.
1754 */
1755 if (entry->priority > SVN_CACHE__MEMBUFFER_LOW_PRIORITY)
1756 drop_hits += entry->hit_count * (apr_uint64_t)entry->priority;
1757
1758 drop_entry(cache, entry);
1759 }
1760 }
1761 }
1762
1763 /* This will never be reached. But if it was, "can't insert" was the
1764 * right answer. */
1765 }
1766
1767 /* This function implements the cache insertion / eviction strategy for L1.
1768 *
1769 * If necessary, enlarge the insertion window of CACHE->L1 by promoting
1770 * entries to L2 until it is at least SIZE bytes long.
1771 *
1772 * Return TRUE if enough room could be found or made. A FALSE result
1773 * indicates that the respective item shall not be added because it is
1774 * too large.
1775 */
1776 static svn_boolean_t
ensure_data_insertable_l1(svn_membuffer_t * cache,apr_size_t size)1777 ensure_data_insertable_l1(svn_membuffer_t *cache, apr_size_t size)
1778 {
1779 /* Guarantees that the while loop will terminate. */
1780 if (size > cache->l1.size)
1781 return FALSE;
1782
1783 /* This loop will eventually terminate because every cache entry
1784 * would get dropped eventually.
1785 */
1786 while (1)
1787 {
1788 /* first offset behind the insertion window
1789 */
1790 apr_uint32_t entry_index = cache->l1.next;
1791 entry_t *entry = get_entry(cache, entry_index);
1792 apr_uint64_t end = cache->l1.next == NO_INDEX
1793 ? cache->l1.start_offset + cache->l1.size
1794 : entry->offset;
1795
1796 /* leave function as soon as the insertion window is large enough
1797 */
1798 if (end - cache->l1.current_data >= size)
1799 return TRUE;
1800
1801 /* Enlarge the insertion window
1802 */
1803 if (cache->l1.next == NO_INDEX)
1804 {
1805 /* We reached the end of the data buffer; restart at the beginning.
1806 * Due to the randomized nature of our LFU implementation, very
1807 * large data items may require multiple passes. Therefore, SIZE
1808 * should be restricted to significantly less than data_size.
1809 */
1810 cache->l1.current_data = cache->l1.start_offset;
1811 cache->l1.next = cache->l1.first;
1812 }
1813 else
1814 {
1815 /* Remove the entry from the end of insertion window and promote
1816 * it to L2, if it is important enough.
1817 */
1818 svn_boolean_t keep = ensure_data_insertable_l2(cache, entry);
1819
1820 /* We might have touched the group that contains ENTRY. Recheck. */
1821 if (entry_index == cache->l1.next)
1822 {
1823 if (keep)
1824 promote_entry(cache, entry);
1825 else
1826 drop_entry(cache, entry);
1827 }
1828 }
1829 }
1830
1831 /* This will never be reached. But if it was, "can't insert" was the
1832 * right answer. */
1833 }
1834
1835 svn_error_t *
svn_cache__membuffer_cache_create(svn_membuffer_t ** cache,apr_size_t total_size,apr_size_t directory_size,apr_size_t segment_count,svn_boolean_t thread_safe,svn_boolean_t allow_blocking_writes,apr_pool_t * pool)1836 svn_cache__membuffer_cache_create(svn_membuffer_t **cache,
1837 apr_size_t total_size,
1838 apr_size_t directory_size,
1839 apr_size_t segment_count,
1840 svn_boolean_t thread_safe,
1841 svn_boolean_t allow_blocking_writes,
1842 apr_pool_t *pool)
1843 {
1844 svn_membuffer_t *c;
1845 prefix_pool_t *prefix_pool;
1846
1847 apr_uint32_t seg;
1848 apr_uint32_t group_count;
1849 apr_uint32_t main_group_count;
1850 apr_uint32_t spare_group_count;
1851 apr_uint32_t group_init_size;
1852 apr_uint64_t data_size;
1853 apr_uint64_t max_entry_size;
1854
1855 /* Allocate 1% of the cache capacity to the prefix string pool.
1856 */
1857 SVN_ERR(prefix_pool_create(&prefix_pool, total_size / 100, thread_safe,
1858 pool));
1859 total_size -= total_size / 100;
1860
1861 /* Limit the total size (only relevant if we can address > 4GB)
1862 */
1863 #if APR_SIZEOF_VOIDP > 4
1864 if (total_size > MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT)
1865 total_size = MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT;
1866 #endif
1867
1868 /* Limit the segment count
1869 */
1870 if (segment_count > MAX_SEGMENT_COUNT)
1871 segment_count = MAX_SEGMENT_COUNT;
1872 if (segment_count * MIN_SEGMENT_SIZE > total_size)
1873 segment_count = total_size / MIN_SEGMENT_SIZE;
1874
1875 /* The segment count must be a power of two. Round it down as necessary.
1876 */
1877 while ((segment_count & (segment_count-1)) != 0)
1878 segment_count &= segment_count-1;
1879
1880 /* if the caller hasn't provided a reasonable segment count or the above
1881 * limitations set it to 0, derive one from the absolute cache size
1882 */
1883 if (segment_count < 1)
1884 {
1885 /* Determine a reasonable number of cache segments. Segmentation is
1886 * only useful for multi-threaded / multi-core servers as it reduces
1887 * lock contention on these systems.
1888 *
1889 * But on these systems, we can assume that ample memory has been
1890 * allocated to this cache. Smaller caches should not be segmented
1891 * as this severely limits the maximum size of cachable items.
1892 *
1893 * Segments should not be smaller than 32MB and max. cachable item
1894 * size should grow as fast as segmentation.
1895 */
1896
1897 apr_uint32_t segment_count_shift = 0;
1898 while (((2 * DEFAULT_MIN_SEGMENT_SIZE) << (2 * segment_count_shift))
1899 < total_size)
1900 ++segment_count_shift;
1901
1902 segment_count = (apr_size_t)1 << segment_count_shift;
1903 }
1904
1905 /* If we have an extremely large cache (>512 GB), the default segment
1906 * size may exceed the amount allocatable as one chunk. In that case,
1907 * increase segmentation until we are under the threshold.
1908 */
1909 while ( total_size / segment_count > MAX_SEGMENT_SIZE
1910 && segment_count < MAX_SEGMENT_COUNT)
1911 segment_count *= 2;
1912
1913 /* allocate cache as an array of segments / cache objects */
1914 c = apr_palloc(pool, segment_count * sizeof(*c));
1915
1916 /* Split total cache size into segments of equal size
1917 */
1918 total_size /= segment_count;
1919 directory_size /= segment_count;
1920
1921 /* prevent pathological conditions: ensure a certain minimum cache size
1922 */
1923 if (total_size < 2 * sizeof(entry_group_t))
1924 total_size = 2 * sizeof(entry_group_t);
1925
1926 /* adapt the dictionary size accordingly, if necessary:
1927 * It must hold at least one group and must not exceed the cache size.
1928 */
1929 if (directory_size > total_size - sizeof(entry_group_t))
1930 directory_size = total_size - sizeof(entry_group_t);
1931 if (directory_size < 2 * sizeof(entry_group_t))
1932 directory_size = 2 * sizeof(entry_group_t);
1933
1934 /* limit the data size to what we can address.
1935 * Note that this cannot overflow since all values are of size_t.
1936 * Also, make it a multiple of the item placement granularity to
1937 * prevent subtle overflows.
1938 */
1939 data_size = ALIGN_VALUE(total_size - directory_size + 1) - ITEM_ALIGNMENT;
1940
1941 /* For cache sizes > 16TB, individual cache segments will be larger
1942 * than 32GB allowing for >4GB entries. But caching chunks larger
1943 * than 4GB are simply not supported.
1944 */
1945 max_entry_size = data_size / 8 > MAX_ITEM_SIZE
1946 ? MAX_ITEM_SIZE
1947 : data_size / 8;
1948
1949 /* to keep the entries small, we use 32 bit indexes only
1950 * -> we need to ensure that no more than 4G entries exist.
1951 *
1952 * Note, that this limit could only be exceeded in a very
1953 * theoretical setup with about 1EB of cache.
1954 */
1955 group_count = directory_size / sizeof(entry_group_t)
1956 >= (APR_UINT32_MAX / GROUP_SIZE)
1957 ? (APR_UINT32_MAX / GROUP_SIZE) - 1
1958 : (apr_uint32_t)(directory_size / sizeof(entry_group_t));
1959
1960 /* set some of the index directory aside as over-flow (spare) buffers */
1961 spare_group_count = MAX(group_count / 4, 1);
1962 main_group_count = group_count - spare_group_count;
1963 assert(spare_group_count > 0 && main_group_count > 0);
1964
1965 group_init_size = 1 + group_count / (8 * GROUP_INIT_GRANULARITY);
1966 for (seg = 0; seg < segment_count; ++seg)
1967 {
1968 /* allocate buffers and initialize cache members
1969 */
1970 c[seg].segment_count = (apr_uint32_t)segment_count;
1971 c[seg].prefix_pool = prefix_pool;
1972
1973 c[seg].group_count = main_group_count;
1974 c[seg].spare_group_count = spare_group_count;
1975 c[seg].first_spare_group = NO_INDEX;
1976 c[seg].max_spare_used = 0;
1977
1978 /* Allocate but don't clear / zero the directory because it would add
1979 significantly to the server start-up time if the caches are large.
1980 Group initialization will take care of that in stead. */
1981 c[seg].directory = apr_palloc(pool,
1982 group_count * sizeof(entry_group_t));
1983
1984 /* Allocate and initialize directory entries as "not initialized",
1985 hence "unused" */
1986 c[seg].group_initialized = apr_pcalloc(pool, group_init_size);
1987
1988 /* Allocate 1/4th of the data buffer to L1
1989 */
1990 c[seg].l1.first = NO_INDEX;
1991 c[seg].l1.last = NO_INDEX;
1992 c[seg].l1.next = NO_INDEX;
1993 c[seg].l1.start_offset = 0;
1994 c[seg].l1.size = ALIGN_VALUE(data_size / 4);
1995 c[seg].l1.current_data = 0;
1996
1997 /* The remaining 3/4th will be used as L2
1998 */
1999 c[seg].l2.first = NO_INDEX;
2000 c[seg].l2.last = NO_INDEX;
2001 c[seg].l2.next = NO_INDEX;
2002 c[seg].l2.start_offset = c[seg].l1.size;
2003 c[seg].l2.size = ALIGN_VALUE(data_size) - c[seg].l1.size;
2004 c[seg].l2.current_data = c[seg].l2.start_offset;
2005
2006 /* This cast is safe because DATA_SIZE <= MAX_SEGMENT_SIZE. */
2007 c[seg].data = apr_palloc(pool, (apr_size_t)ALIGN_VALUE(data_size));
2008 c[seg].data_used = 0;
2009 c[seg].max_entry_size = max_entry_size;
2010
2011 c[seg].used_entries = 0;
2012 c[seg].total_reads = 0;
2013 c[seg].total_writes = 0;
2014 c[seg].total_hits = 0;
2015
2016 /* were allocations successful?
2017 * If not, initialize a minimal cache structure.
2018 */
2019 if (c[seg].data == NULL || c[seg].directory == NULL)
2020 {
2021 /* We are OOM. There is no need to proceed with "half a cache".
2022 */
2023 return svn_error_wrap_apr(APR_ENOMEM, "OOM");
2024 }
2025
2026 #if (APR_HAS_THREADS && USE_SIMPLE_MUTEX)
2027 /* A lock for intra-process synchronization to the cache, or NULL if
2028 * the cache's creator doesn't feel the cache needs to be
2029 * thread-safe.
2030 */
2031 SVN_ERR(svn_mutex__init(&c[seg].lock, thread_safe, pool));
2032 #elif (APR_HAS_THREADS && !USE_SIMPLE_MUTEX)
2033 /* Same for read-write lock. */
2034 c[seg].lock = NULL;
2035 if (thread_safe)
2036 {
2037 apr_status_t status =
2038 apr_thread_rwlock_create(&(c[seg].lock), pool);
2039 if (status)
2040 return svn_error_wrap_apr(status, _("Can't create cache mutex"));
2041 }
2042
2043 /* Select the behavior of write operations.
2044 */
2045 c[seg].allow_blocking_writes = allow_blocking_writes;
2046 #endif
2047 /* No writers at the moment. */
2048 c[seg].write_lock_count = 0;
2049 }
2050
2051 /* done here
2052 */
2053 *cache = c;
2054 return SVN_NO_ERROR;
2055 }
2056
2057 svn_error_t *
svn_cache__membuffer_clear(svn_membuffer_t * cache)2058 svn_cache__membuffer_clear(svn_membuffer_t *cache)
2059 {
2060 apr_size_t seg;
2061 apr_size_t segment_count = cache->segment_count;
2062
2063 /* Length of the group_initialized array in bytes.
2064 See also svn_cache__membuffer_cache_create(). */
2065 apr_size_t group_init_size
2066 = 1 + (cache->group_count + cache->spare_group_count)
2067 / (8 * GROUP_INIT_GRANULARITY);
2068
2069 /* Clear segment by segment. This implies that other thread may read
2070 and write to other segments after we cleared them and before the
2071 last segment is done.
2072
2073 However, that is no different from a write request coming through
2074 right after we cleared all segments because dependencies between
2075 cache entries (recursive lookup / access locks) are not allowed.
2076 */
2077 for (seg = 0; seg < segment_count; ++seg)
2078 {
2079 /* Unconditionally acquire the write lock. */
2080 SVN_ERR(force_write_lock_cache(&cache[seg]));
2081
2082 /* Mark all groups as "not initialized", which implies "empty". */
2083 cache[seg].first_spare_group = NO_INDEX;
2084 cache[seg].max_spare_used = 0;
2085
2086 memset(cache[seg].group_initialized, 0, group_init_size);
2087
2088 /* Unlink L1 contents. */
2089 cache[seg].l1.first = NO_INDEX;
2090 cache[seg].l1.last = NO_INDEX;
2091 cache[seg].l1.next = NO_INDEX;
2092 cache[seg].l1.current_data = cache[seg].l1.start_offset;
2093
2094 /* Unlink L2 contents. */
2095 cache[seg].l2.first = NO_INDEX;
2096 cache[seg].l2.last = NO_INDEX;
2097 cache[seg].l2.next = NO_INDEX;
2098 cache[seg].l2.current_data = cache[seg].l2.start_offset;
2099
2100 /* Reset content counters. */
2101 cache[seg].data_used = 0;
2102 cache[seg].used_entries = 0;
2103
2104 /* Segment may be used again. */
2105 SVN_ERR(unlock_cache(&cache[seg], SVN_NO_ERROR));
2106 }
2107
2108 /* done here */
2109 return SVN_NO_ERROR;
2110 }
2111
2112 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
2113 * by the hash value TO_FIND and set *FOUND accordingly.
2114 *
2115 * Note: This function requires the caller to serialize access.
2116 * Don't call it directly, call entry_exists instead.
2117 */
2118 static svn_error_t *
entry_exists_internal(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,svn_boolean_t * found)2119 entry_exists_internal(svn_membuffer_t *cache,
2120 apr_uint32_t group_index,
2121 const full_key_t *to_find,
2122 svn_boolean_t *found)
2123 {
2124 *found = find_entry(cache, group_index, to_find, FALSE) != NULL;
2125 return SVN_NO_ERROR;
2126 }
2127
2128 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
2129 * by the hash value TO_FIND and set *FOUND accordingly.
2130 */
2131 static svn_error_t *
entry_exists(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,svn_boolean_t * found)2132 entry_exists(svn_membuffer_t *cache,
2133 apr_uint32_t group_index,
2134 const full_key_t *to_find,
2135 svn_boolean_t *found)
2136 {
2137 WITH_READ_LOCK(cache,
2138 entry_exists_internal(cache,
2139 group_index,
2140 to_find,
2141 found));
2142
2143 return SVN_NO_ERROR;
2144 }
2145
2146 /* Given the SIZE and PRIORITY of a new item, return the cache level
2147 (L1 or L2) in fragment CACHE that this item shall be inserted into.
2148 If we can't find nor make enough room for the item, return NULL.
2149 */
2150 static cache_level_t *
select_level(svn_membuffer_t * cache,apr_size_t size,apr_uint32_t priority)2151 select_level(svn_membuffer_t *cache,
2152 apr_size_t size,
2153 apr_uint32_t priority)
2154 {
2155 if (cache->max_entry_size >= size)
2156 {
2157 /* Small items go into L1. */
2158 return ensure_data_insertable_l1(cache, size)
2159 ? &cache->l1
2160 : NULL;
2161 }
2162 else if ( cache->l2.size >= size
2163 && MAX_ITEM_SIZE >= size
2164 && priority > SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY)
2165 {
2166 /* Large but important items go into L2. */
2167 entry_t dummy_entry = { { { 0 } } };
2168 dummy_entry.priority = priority;
2169 dummy_entry.size = size;
2170
2171 return ensure_data_insertable_l2(cache, &dummy_entry)
2172 ? &cache->l2
2173 : NULL;
2174 }
2175
2176 /* Don't cache large, unimportant items. */
2177 return NULL;
2178 }
2179
2180 /* Try to insert the serialized item given in BUFFER with ITEM_SIZE
2181 * into the group GROUP_INDEX of CACHE and uniquely identify it by
2182 * hash value TO_FIND.
2183 *
2184 * However, there is no guarantee that it will actually be put into
2185 * the cache. If there is already some data associated with TO_FIND,
2186 * it will be removed from the cache even if the new data cannot
2187 * be inserted.
2188 *
2189 * Note: This function requires the caller to serialization access.
2190 * Don't call it directly, call membuffer_cache_set instead.
2191 */
2192 static svn_error_t *
membuffer_cache_set_internal(svn_membuffer_t * cache,const full_key_t * to_find,apr_uint32_t group_index,char * buffer,apr_size_t item_size,apr_uint32_t priority,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * scratch_pool)2193 membuffer_cache_set_internal(svn_membuffer_t *cache,
2194 const full_key_t *to_find,
2195 apr_uint32_t group_index,
2196 char *buffer,
2197 apr_size_t item_size,
2198 apr_uint32_t priority,
2199 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2200 apr_pool_t *scratch_pool)
2201 {
2202 cache_level_t *level;
2203 apr_size_t size;
2204
2205 /* first, look for a previous entry for the given key */
2206 entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
2207
2208 /* If this one fails, you are using multiple threads but created the
2209 * membuffer in single-threaded mode. */
2210 assert(0 == svn_atomic_inc(&cache->write_lock_count));
2211
2212 /* Quick check make sure arithmetics will work further down the road. */
2213 size = item_size + to_find->entry_key.key_len;
2214 if (size < item_size)
2215 {
2216 /* Arithmetic overflow, so combination of serialized ITEM and KEY
2217 * cannot not fit into the cache. Setting BUFFER to NULL will cause
2218 * the removal of any entry if that exists without writing new data. */
2219 buffer = NULL;
2220 }
2221
2222 /* if there is an old version of that entry and the new data fits into
2223 * the old spot, just re-use that space. */
2224 if (entry && buffer && ALIGN_VALUE(entry->size) >= size)
2225 {
2226 /* Careful! We need to cast SIZE to the full width of CACHE->DATA_USED
2227 * lest we run into trouble with 32 bit underflow *not* treated as a
2228 * negative value.
2229 */
2230 cache->data_used += (apr_uint64_t)size - entry->size;
2231 entry->size = size;
2232 entry->priority = priority;
2233
2234 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2235
2236 /* Remember original content, type and key (hashes)
2237 */
2238 SVN_ERR(store_content_part(tag, buffer, item_size, scratch_pool));
2239 memcpy(&entry->tag, tag, sizeof(*tag));
2240
2241 #endif
2242
2243 if (entry->key.key_len)
2244 memcpy(cache->data + entry->offset, to_find->full_key.data,
2245 entry->key.key_len);
2246 if (item_size)
2247 memcpy(cache->data + entry->offset + entry->key.key_len, buffer,
2248 item_size);
2249
2250 cache->total_writes++;
2251
2252 /* Putting the decrement into an assert() to make it disappear
2253 * in production code. */
2254 assert(0 == svn_atomic_dec(&cache->write_lock_count));
2255 return SVN_NO_ERROR;
2256 }
2257
2258 /* if necessary, enlarge the insertion window.
2259 */
2260 level = buffer ? select_level(cache, size, priority) : NULL;
2261 if (level)
2262 {
2263 /* Remove old data for this key, if that exists.
2264 * Get an unused entry for the key and and initialize it with
2265 * the serialized item's (future) position within data buffer.
2266 */
2267 entry = find_entry(cache, group_index, to_find, TRUE);
2268 entry->size = size;
2269 entry->offset = level->current_data;
2270 entry->priority = priority;
2271
2272 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2273
2274 /* Remember original content, type and key (hashes)
2275 */
2276 SVN_ERR(store_content_part(tag, buffer, item_size, scratch_pool));
2277 memcpy(&entry->tag, tag, sizeof(*tag));
2278
2279 #endif
2280
2281 /* Link the entry properly.
2282 */
2283 insert_entry(cache, entry);
2284
2285 /* Copy the serialized item data into the cache.
2286 */
2287 if (entry->key.key_len)
2288 memcpy(cache->data + entry->offset, to_find->full_key.data,
2289 entry->key.key_len);
2290 if (item_size)
2291 memcpy(cache->data + entry->offset + entry->key.key_len, buffer,
2292 item_size);
2293
2294 cache->total_writes++;
2295 }
2296 else
2297 {
2298 /* if there is already an entry for this key, drop it.
2299 * Since ensure_data_insertable may have removed entries from
2300 * ENTRY's group, re-do the lookup.
2301 */
2302 entry = find_entry(cache, group_index, to_find, FALSE);
2303 if (entry)
2304 drop_entry(cache, entry);
2305 }
2306
2307 /* Putting the decrement into an assert() to make it disappear
2308 * in production code. */
2309 assert(0 == svn_atomic_dec(&cache->write_lock_count));
2310 return SVN_NO_ERROR;
2311 }
2312
2313 /* Try to insert the ITEM and use the KEY to uniquely identify it.
2314 * However, there is no guarantee that it will actually be put into
2315 * the cache. If there is already some data associated to the KEY,
2316 * it will be removed from the cache even if the new data cannot
2317 * be inserted.
2318 *
2319 * The SERIALIZER is called to transform the ITEM into a single,
2320 * flat data buffer. Temporary allocations may be done in POOL.
2321 */
2322 static svn_error_t *
membuffer_cache_set(svn_membuffer_t * cache,const full_key_t * key,void * item,svn_cache__serialize_func_t serializer,apr_uint32_t priority,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * scratch_pool)2323 membuffer_cache_set(svn_membuffer_t *cache,
2324 const full_key_t *key,
2325 void *item,
2326 svn_cache__serialize_func_t serializer,
2327 apr_uint32_t priority,
2328 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2329 apr_pool_t *scratch_pool)
2330 {
2331 apr_uint32_t group_index;
2332 void *buffer = NULL;
2333 apr_size_t size = 0;
2334
2335 /* find the entry group that will hold the key.
2336 */
2337 group_index = get_group_index(&cache, &key->entry_key);
2338
2339 /* Serialize data data.
2340 */
2341 if (item)
2342 SVN_ERR(serializer(&buffer, &size, item, scratch_pool));
2343
2344 /* The actual cache data access needs to sync'ed
2345 */
2346 WITH_WRITE_LOCK(cache,
2347 membuffer_cache_set_internal(cache,
2348 key,
2349 group_index,
2350 buffer,
2351 size,
2352 priority,
2353 DEBUG_CACHE_MEMBUFFER_TAG
2354 scratch_pool));
2355 return SVN_NO_ERROR;
2356 }
2357
2358 /* Count a hit in ENTRY within CACHE.
2359 */
2360 static void
increment_hit_counters(svn_membuffer_t * cache,entry_t * entry)2361 increment_hit_counters(svn_membuffer_t *cache, entry_t *entry)
2362 {
2363 /* To minimize the memory footprint of the cache index, we limit local
2364 * hit counters to 32 bits. These may overflow but we don't really
2365 * care because at worst, ENTRY will be dropped from cache once every
2366 * few billion hits. */
2367 svn_atomic_inc(&entry->hit_count);
2368
2369 /* That one is for stats only. */
2370 cache->total_hits++;
2371 }
2372
2373 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
2374 * by the hash value TO_FIND. If no item has been stored for KEY,
2375 * *BUFFER will be NULL. Otherwise, return a copy of the serialized
2376 * data in *BUFFER and return its size in *ITEM_SIZE. Allocations will
2377 * be done in POOL.
2378 *
2379 * Note: This function requires the caller to serialization access.
2380 * Don't call it directly, call membuffer_cache_get instead.
2381 */
2382 static svn_error_t *
membuffer_cache_get_internal(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,char ** buffer,apr_size_t * item_size,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * result_pool)2383 membuffer_cache_get_internal(svn_membuffer_t *cache,
2384 apr_uint32_t group_index,
2385 const full_key_t *to_find,
2386 char **buffer,
2387 apr_size_t *item_size,
2388 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2389 apr_pool_t *result_pool)
2390 {
2391 entry_t *entry;
2392 apr_size_t size;
2393
2394 /* The actual cache data access needs to sync'ed
2395 */
2396 entry = find_entry(cache, group_index, to_find, FALSE);
2397 cache->total_reads++;
2398 if (entry == NULL)
2399 {
2400 /* no such entry found.
2401 */
2402 *buffer = NULL;
2403 *item_size = 0;
2404
2405 return SVN_NO_ERROR;
2406 }
2407
2408 size = ALIGN_VALUE(entry->size) - entry->key.key_len;
2409 *buffer = apr_palloc(result_pool, size);
2410 memcpy(*buffer, cache->data + entry->offset + entry->key.key_len, size);
2411
2412 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2413
2414 /* Check for overlapping entries.
2415 */
2416 SVN_ERR_ASSERT(entry->next == NO_INDEX ||
2417 entry->offset + size
2418 <= get_entry(cache, entry->next)->offset);
2419
2420 /* Compare original content, type and key (hashes)
2421 */
2422 SVN_ERR(store_content_part(tag, *buffer, entry->size - entry->key.key_len,
2423 result_pool));
2424 SVN_ERR(assert_equal_tags(&entry->tag, tag));
2425
2426 #endif
2427
2428 /* update hit statistics
2429 */
2430 increment_hit_counters(cache, entry);
2431 *item_size = entry->size - entry->key.key_len;
2432
2433 return SVN_NO_ERROR;
2434 }
2435
2436 /* Look for the *ITEM identified by KEY. If no item has been stored
2437 * for KEY, *ITEM will be NULL. Otherwise, the DESERIALIZER is called
2438 * to re-construct the proper object from the serialized data.
2439 * Allocations will be done in POOL.
2440 */
2441 static svn_error_t *
membuffer_cache_get(svn_membuffer_t * cache,const full_key_t * key,void ** item,svn_cache__deserialize_func_t deserializer,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * result_pool)2442 membuffer_cache_get(svn_membuffer_t *cache,
2443 const full_key_t *key,
2444 void **item,
2445 svn_cache__deserialize_func_t deserializer,
2446 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2447 apr_pool_t *result_pool)
2448 {
2449 apr_uint32_t group_index;
2450 char *buffer;
2451 apr_size_t size;
2452
2453 /* find the entry group that will hold the key.
2454 */
2455 group_index = get_group_index(&cache, &key->entry_key);
2456 WITH_READ_LOCK(cache,
2457 membuffer_cache_get_internal(cache,
2458 group_index,
2459 key,
2460 &buffer,
2461 &size,
2462 DEBUG_CACHE_MEMBUFFER_TAG
2463 result_pool));
2464
2465 /* re-construct the original data object from its serialized form.
2466 */
2467 if (buffer == NULL)
2468 {
2469 *item = NULL;
2470 return SVN_NO_ERROR;
2471 }
2472
2473 return deserializer(item, buffer, size, result_pool);
2474 }
2475
2476 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
2477 * by the hash value TO_FIND. If no item has been stored for KEY, *FOUND
2478 * will be FALSE and TRUE otherwise.
2479 */
2480 static svn_error_t *
membuffer_cache_has_key_internal(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,svn_boolean_t * found)2481 membuffer_cache_has_key_internal(svn_membuffer_t *cache,
2482 apr_uint32_t group_index,
2483 const full_key_t *to_find,
2484 svn_boolean_t *found)
2485 {
2486 entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
2487 if (entry)
2488 {
2489 /* This often be called by "block read" when most data is already
2490 in L2 and only a few previously evicted items are added to L1
2491 again. While items in L1 are well protected for a while, L2
2492 items may get evicted soon. Thus, mark all them as "hit" to give
2493 them a higher chance of survival. */
2494 increment_hit_counters(cache, entry);
2495 *found = TRUE;
2496 }
2497 else
2498 {
2499 *found = FALSE;
2500 }
2501
2502 return SVN_NO_ERROR;
2503 }
2504
2505 /* Look for an entry identified by KEY. If no item has been stored
2506 * for KEY, *FOUND will be set to FALSE and TRUE otherwise.
2507 */
2508 /* Implements svn_cache__has_key for membuffer caches.
2509 */
2510 static svn_error_t *
membuffer_cache_has_key(svn_membuffer_t * cache,const full_key_t * key,svn_boolean_t * found)2511 membuffer_cache_has_key(svn_membuffer_t *cache,
2512 const full_key_t *key,
2513 svn_boolean_t *found)
2514 {
2515 /* find the entry group that will hold the key.
2516 */
2517 apr_uint32_t group_index = get_group_index(&cache, &key->entry_key);
2518 cache->total_reads++;
2519
2520 WITH_READ_LOCK(cache,
2521 membuffer_cache_has_key_internal(cache,
2522 group_index,
2523 key,
2524 found));
2525
2526 return SVN_NO_ERROR;
2527 }
2528
2529 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
2530 * by the hash value TO_FIND. FOUND indicates whether that entry exists.
2531 * If not found, *ITEM will be NULL.
2532 *
2533 * Otherwise, the DESERIALIZER is called with that entry and the BATON
2534 * provided and will extract the desired information. The result is set
2535 * in *ITEM. Allocations will be done in POOL.
2536 *
2537 * Note: This function requires the caller to serialization access.
2538 * Don't call it directly, call membuffer_cache_get_partial instead.
2539 */
2540 static svn_error_t *
membuffer_cache_get_partial_internal(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,void ** item,svn_boolean_t * found,svn_cache__partial_getter_func_t deserializer,void * baton,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * result_pool)2541 membuffer_cache_get_partial_internal(svn_membuffer_t *cache,
2542 apr_uint32_t group_index,
2543 const full_key_t *to_find,
2544 void **item,
2545 svn_boolean_t *found,
2546 svn_cache__partial_getter_func_t deserializer,
2547 void *baton,
2548 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2549 apr_pool_t *result_pool)
2550 {
2551 entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
2552 cache->total_reads++;
2553 if (entry == NULL)
2554 {
2555 *item = NULL;
2556 *found = FALSE;
2557
2558 return SVN_NO_ERROR;
2559 }
2560 else
2561 {
2562 const void *item_data = cache->data + entry->offset + entry->key.key_len;
2563 apr_size_t item_size = entry->size - entry->key.key_len;
2564 *found = TRUE;
2565 increment_hit_counters(cache, entry);
2566
2567 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2568
2569 /* Check for overlapping entries.
2570 */
2571 SVN_ERR_ASSERT(entry->next == NO_INDEX ||
2572 entry->offset + entry->size
2573 <= get_entry(cache, entry->next)->offset);
2574
2575 /* Compare original content, type and key (hashes)
2576 */
2577 SVN_ERR(store_content_part(tag, item_data, item_size, result_pool));
2578 SVN_ERR(assert_equal_tags(&entry->tag, tag));
2579
2580 #endif
2581
2582 return deserializer(item, item_data, item_size, baton, result_pool);
2583 }
2584 }
2585
2586 /* Look for the cache entry identified by KEY. FOUND indicates
2587 * whether that entry exists. If not found, *ITEM will be NULL. Otherwise,
2588 * the DESERIALIZER is called with that entry and the BATON provided
2589 * and will extract the desired information. The result is set in *ITEM.
2590 * Allocations will be done in POOL.
2591 */
2592 static svn_error_t *
membuffer_cache_get_partial(svn_membuffer_t * cache,const full_key_t * key,void ** item,svn_boolean_t * found,svn_cache__partial_getter_func_t deserializer,void * baton,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * result_pool)2593 membuffer_cache_get_partial(svn_membuffer_t *cache,
2594 const full_key_t *key,
2595 void **item,
2596 svn_boolean_t *found,
2597 svn_cache__partial_getter_func_t deserializer,
2598 void *baton,
2599 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2600 apr_pool_t *result_pool)
2601 {
2602 apr_uint32_t group_index = get_group_index(&cache, &key->entry_key);
2603
2604 WITH_READ_LOCK(cache,
2605 membuffer_cache_get_partial_internal
2606 (cache, group_index, key, item, found,
2607 deserializer, baton, DEBUG_CACHE_MEMBUFFER_TAG
2608 result_pool));
2609
2610 return SVN_NO_ERROR;
2611 }
2612
2613 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
2614 * by the hash value TO_FIND. If no entry has been found, the function
2615 * returns without modifying the cache.
2616 *
2617 * Otherwise, FUNC is called with that entry and the BATON provided
2618 * and may modify the cache entry. Allocations will be done in POOL.
2619 *
2620 * Note: This function requires the caller to serialization access.
2621 * Don't call it directly, call membuffer_cache_set_partial instead.
2622 */
2623 static svn_error_t *
membuffer_cache_set_partial_internal(svn_membuffer_t * cache,apr_uint32_t group_index,const full_key_t * to_find,svn_cache__partial_setter_func_t func,void * baton,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * scratch_pool)2624 membuffer_cache_set_partial_internal(svn_membuffer_t *cache,
2625 apr_uint32_t group_index,
2626 const full_key_t *to_find,
2627 svn_cache__partial_setter_func_t func,
2628 void *baton,
2629 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2630 apr_pool_t *scratch_pool)
2631 {
2632 /* cache item lookup
2633 */
2634 entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
2635 cache->total_reads++;
2636
2637 /* this function is a no-op if the item is not in cache
2638 */
2639 if (entry != NULL)
2640 {
2641 svn_error_t *err;
2642
2643 /* access the serialized cache item */
2644 apr_size_t key_len = entry->key.key_len;
2645 void *item_data = cache->data + entry->offset + key_len;
2646 void *orig_data = item_data;
2647 apr_size_t item_size = entry->size - key_len;
2648
2649 increment_hit_counters(cache, entry);
2650 cache->total_writes++;
2651
2652 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2653
2654 /* Check for overlapping entries.
2655 */
2656 SVN_ERR_ASSERT(entry->next == NO_INDEX ||
2657 entry->offset + entry->size
2658 <= get_entry(cache, entry->next)->offset);
2659
2660 /* Compare original content, type and key (hashes)
2661 */
2662 SVN_ERR(store_content_part(tag, item_data, item_size, scratch_pool));
2663 SVN_ERR(assert_equal_tags(&entry->tag, tag));
2664
2665 #endif
2666
2667 /* modify it, preferably in-situ.
2668 */
2669 err = func(&item_data, &item_size, baton, scratch_pool);
2670
2671 if (err)
2672 {
2673 /* Something somewhere when wrong while FUNC was modifying the
2674 * changed item. Thus, it might have become invalid /corrupted.
2675 * We better drop that.
2676 */
2677 drop_entry(cache, entry);
2678
2679 return err;
2680 }
2681 else
2682 {
2683 /* if the modification caused a re-allocation, we need to remove
2684 * the old entry and to copy the new data back into cache.
2685 */
2686 if (item_data != orig_data)
2687 {
2688 /* Remove the old entry and try to make space for the new one.
2689 * Note that the key has already been stored in the past, i.e.
2690 * it is shorter than the MAX_ENTRY_SIZE.
2691 */
2692 drop_entry(cache, entry);
2693 if ( (cache->max_entry_size - key_len >= item_size)
2694 && ensure_data_insertable_l1(cache, item_size + key_len))
2695 {
2696 /* Write the new entry.
2697 */
2698 entry = find_entry(cache, group_index, to_find, TRUE);
2699 entry->size = item_size + key_len;
2700 entry->offset = cache->l1.current_data;
2701
2702 if (key_len)
2703 memcpy(cache->data + entry->offset,
2704 to_find->full_key.data, key_len);
2705 if (item_size)
2706 memcpy(cache->data + entry->offset + key_len, item_data,
2707 item_size);
2708
2709 /* Link the entry properly.
2710 */
2711 insert_entry(cache, entry);
2712 }
2713 }
2714
2715 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2716
2717 /* Remember original content, type and key (hashes)
2718 */
2719 SVN_ERR(store_content_part(tag, item_data, item_size, scratch_pool));
2720 memcpy(&entry->tag, tag, sizeof(*tag));
2721
2722 #endif
2723 }
2724 }
2725
2726 return SVN_NO_ERROR;
2727 }
2728
2729 /* Look for the cache entry identified by KEY. If no entry
2730 * has been found, the function returns without modifying the cache.
2731 * Otherwise, FUNC is called with that entry and the BATON provided
2732 * and may modify the cache entry. Allocations will be done in POOL.
2733 */
2734 static svn_error_t *
membuffer_cache_set_partial(svn_membuffer_t * cache,const full_key_t * key,svn_cache__partial_setter_func_t func,void * baton,DEBUG_CACHE_MEMBUFFER_TAG_ARG apr_pool_t * scratch_pool)2735 membuffer_cache_set_partial(svn_membuffer_t *cache,
2736 const full_key_t *key,
2737 svn_cache__partial_setter_func_t func,
2738 void *baton,
2739 DEBUG_CACHE_MEMBUFFER_TAG_ARG
2740 apr_pool_t *scratch_pool)
2741 {
2742 /* cache item lookup
2743 */
2744 apr_uint32_t group_index = get_group_index(&cache, &key->entry_key);
2745 WITH_WRITE_LOCK(cache,
2746 membuffer_cache_set_partial_internal
2747 (cache, group_index, key, func, baton,
2748 DEBUG_CACHE_MEMBUFFER_TAG
2749 scratch_pool));
2750
2751 /* done here -> unlock the cache
2752 */
2753 return SVN_NO_ERROR;
2754 }
2755
2756 /* Implement the svn_cache__t interface on top of a shared membuffer cache.
2757 *
2758 * Because membuffer caches tend to be very large, there will be rather few
2759 * of them (usually only one). Thus, the same instance shall be used as the
2760 * backend to many application-visible svn_cache__t instances. This should
2761 * also achieve global resource usage fairness.
2762 *
2763 * To accommodate items from multiple resources, the individual keys must be
2764 * unique over all sources. This is achieved by simply adding a prefix key
2765 * that unambiguously identifies the item's context (e.g. path to the
2766 * respective repository). The prefix will be set upon construction of the
2767 * svn_cache__t instance.
2768 */
2769
2770 /* Internal cache structure (used in svn_cache__t.cache_internal) basically
2771 * holding the additional parameters needed to call the respective membuffer
2772 * functions.
2773 */
2774 typedef struct svn_membuffer_cache_t
2775 {
2776 /* this is where all our data will end up in
2777 */
2778 svn_membuffer_t *membuffer;
2779
2780 /* use this conversion function when inserting an item into the memcache
2781 */
2782 svn_cache__serialize_func_t serializer;
2783
2784 /* use this conversion function when reading an item from the memcache
2785 */
2786 svn_cache__deserialize_func_t deserializer;
2787
2788 /* Prepend this to any key passed to us.
2789 * This makes our keys different from all keys used by svn_membuffer_cache_t
2790 * instances that we don't want to share cached data with.
2791 */
2792 entry_key_t prefix;
2793
2794 /* length of the keys that will be passed to us through the
2795 * svn_cache_t interface. May be APR_HASH_KEY_STRING.
2796 */
2797 apr_ssize_t key_len;
2798
2799 /* priority class for all items written through this interface */
2800 apr_uint32_t priority;
2801
2802 /* Temporary buffer containing the hash key for the current access
2803 */
2804 full_key_t combined_key;
2805
2806 /* if enabled, this will serialize the access to this instance.
2807 */
2808 svn_mutex__t *mutex;
2809 } svn_membuffer_cache_t;
2810
2811 /* Return the prefix key used by CACHE. */
2812 static const char *
get_prefix_key(const svn_membuffer_cache_t * cache)2813 get_prefix_key(const svn_membuffer_cache_t *cache)
2814 {
2815 return (cache->prefix.prefix_idx == NO_INDEX
2816 ? cache->combined_key.full_key.data
2817 : cache->membuffer->prefix_pool->values[cache->prefix.prefix_idx]);
2818 }
2819
2820 /* Basically calculate a hash value for KEY of length KEY_LEN, combine it
2821 * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY.
2822 * This could replace combine_key() entirely but we actually use it only
2823 * when the quick path failed.
2824 */
2825 static void
combine_long_key(svn_membuffer_cache_t * cache,const void * key,apr_ssize_t key_len)2826 combine_long_key(svn_membuffer_cache_t *cache,
2827 const void *key,
2828 apr_ssize_t key_len)
2829 {
2830 apr_uint32_t *digest_buffer;
2831 char *key_copy;
2832 apr_size_t prefix_len = cache->prefix.key_len;
2833 apr_size_t aligned_key_len;
2834
2835 /* handle variable-length keys */
2836 if (key_len == APR_HASH_KEY_STRING)
2837 key_len = strlen((const char *) key);
2838
2839 aligned_key_len = ALIGN_VALUE(key_len);
2840
2841 /* Combine keys. */
2842 svn_membuf__ensure(&cache->combined_key.full_key,
2843 aligned_key_len + prefix_len);
2844
2845 key_copy = (char *)cache->combined_key.full_key.data + prefix_len;
2846 cache->combined_key.entry_key.key_len = aligned_key_len + prefix_len;
2847 memcpy(key_copy, key, key_len);
2848 memset(key_copy + key_len, 0, aligned_key_len - key_len);
2849
2850 /* Hash key into 16 bytes. */
2851 digest_buffer = (apr_uint32_t *)cache->combined_key.entry_key.fingerprint;
2852 svn__fnv1a_32x4_raw(digest_buffer, key, key_len);
2853
2854 /* Combine with prefix. */
2855 cache->combined_key.entry_key.fingerprint[0]
2856 ^= cache->prefix.fingerprint[0];
2857 cache->combined_key.entry_key.fingerprint[1]
2858 ^= cache->prefix.fingerprint[1];
2859 }
2860
2861 /* Basically calculate a hash value for KEY of length KEY_LEN, combine it
2862 * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY.
2863 */
2864 static void
combine_key(svn_membuffer_cache_t * cache,const void * key,apr_ssize_t key_len)2865 combine_key(svn_membuffer_cache_t *cache,
2866 const void *key,
2867 apr_ssize_t key_len)
2868 {
2869 /* copy of *key, padded with 0 */
2870 apr_uint64_t data[2];
2871
2872 /* Do we have to compare full keys? */
2873 if (cache->prefix.prefix_idx == NO_INDEX)
2874 {
2875 combine_long_key(cache, key, key_len);
2876 return;
2877 }
2878
2879 /* short, fixed-size keys are the most common case */
2880 if (key_len == 16)
2881 {
2882 memcpy(data, key, 16);
2883 }
2884 else if (key_len == 8)
2885 {
2886 memcpy(data, key, 8);
2887 data[1] = 0;
2888 }
2889 else
2890 {
2891 assert(key_len != APR_HASH_KEY_STRING && key_len < 16);
2892 data[0] = 0;
2893 data[1] = 0;
2894 memcpy(data, key, key_len);
2895 }
2896
2897 /* Scramble key DATA to spread the key space more evenly across the
2898 * cache segments and entry buckets. All of this shall be reversible
2899 * to prevent key collisions. So, we limit ourselves to xor and
2900 * permutations.
2901 *
2902 * Since the entry key must preserve the full key (prefix and KEY),
2903 * the scramble must not introduce KEY collisions.
2904 */
2905 data[1] = (data[1] << 27) | (data[1] >> 37);
2906 data[1] ^= data[0] & 0xffff;
2907 data[0] ^= data[1] & APR_UINT64_C(0xffffffffffff0000);
2908
2909 /* Combine with this cache's prefix. This is reversible because the
2910 * prefix is known through to the respective entry_key element. So,
2911 * knowing entry_key.prefix_id, we can still reconstruct KEY (and the
2912 * prefix key).
2913 */
2914 cache->combined_key.entry_key.fingerprint[0]
2915 = data[0] ^ cache->prefix.fingerprint[0];
2916 cache->combined_key.entry_key.fingerprint[1]
2917 = data[1] ^ cache->prefix.fingerprint[1];
2918 }
2919
2920 /* Implement svn_cache__vtable_t.get (not thread-safe)
2921 */
2922 static svn_error_t *
svn_membuffer_cache_get(void ** value_p,svn_boolean_t * found,void * cache_void,const void * key,apr_pool_t * result_pool)2923 svn_membuffer_cache_get(void **value_p,
2924 svn_boolean_t *found,
2925 void *cache_void,
2926 const void *key,
2927 apr_pool_t *result_pool)
2928 {
2929 svn_membuffer_cache_t *cache = cache_void;
2930
2931 DEBUG_CACHE_MEMBUFFER_INIT_TAG(result_pool)
2932
2933 /* special case */
2934 if (key == NULL)
2935 {
2936 *value_p = NULL;
2937 *found = FALSE;
2938
2939 return SVN_NO_ERROR;
2940 }
2941
2942 /* construct the full, i.e. globally unique, key by adding
2943 * this cache instances' prefix
2944 */
2945 combine_key(cache, key, cache->key_len);
2946
2947 /* Look the item up. */
2948 SVN_ERR(membuffer_cache_get(cache->membuffer,
2949 &cache->combined_key,
2950 value_p,
2951 cache->deserializer,
2952 DEBUG_CACHE_MEMBUFFER_TAG
2953 result_pool));
2954
2955 /* return result */
2956 *found = *value_p != NULL;
2957
2958 return SVN_NO_ERROR;
2959 }
2960
2961 /* Implement svn_cache__vtable_t.has_key (not thread-safe)
2962 */
2963 static svn_error_t *
svn_membuffer_cache_has_key(svn_boolean_t * found,void * cache_void,const void * key,apr_pool_t * scratch_pool)2964 svn_membuffer_cache_has_key(svn_boolean_t *found,
2965 void *cache_void,
2966 const void *key,
2967 apr_pool_t *scratch_pool)
2968 {
2969 svn_membuffer_cache_t *cache = cache_void;
2970
2971 /* special case */
2972 if (key == NULL)
2973 {
2974 *found = FALSE;
2975
2976 return SVN_NO_ERROR;
2977 }
2978
2979 /* construct the full, i.e. globally unique, key by adding
2980 * this cache instances' prefix
2981 */
2982 combine_key(cache, key, cache->key_len);
2983
2984 /* Look the item up. */
2985 SVN_ERR(membuffer_cache_has_key(cache->membuffer,
2986 &cache->combined_key,
2987 found));
2988
2989 /* return result */
2990 return SVN_NO_ERROR;
2991 }
2992
2993 /* Implement svn_cache__vtable_t.set (not thread-safe)
2994 */
2995 static svn_error_t *
svn_membuffer_cache_set(void * cache_void,const void * key,void * value,apr_pool_t * scratch_pool)2996 svn_membuffer_cache_set(void *cache_void,
2997 const void *key,
2998 void *value,
2999 apr_pool_t *scratch_pool)
3000 {
3001 svn_membuffer_cache_t *cache = cache_void;
3002
3003 DEBUG_CACHE_MEMBUFFER_INIT_TAG(scratch_pool)
3004
3005 /* special case */
3006 if (key == NULL)
3007 return SVN_NO_ERROR;
3008
3009 /* construct the full, i.e. globally unique, key by adding
3010 * this cache instances' prefix
3011 */
3012 combine_key(cache, key, cache->key_len);
3013
3014 /* (probably) add the item to the cache. But there is no real guarantee
3015 * that the item will actually be cached afterwards.
3016 */
3017 return membuffer_cache_set(cache->membuffer,
3018 &cache->combined_key,
3019 value,
3020 cache->serializer,
3021 cache->priority,
3022 DEBUG_CACHE_MEMBUFFER_TAG
3023 scratch_pool);
3024 }
3025
3026 /* Implement svn_cache__vtable_t.iter as "not implemented"
3027 */
3028 static svn_error_t *
svn_membuffer_cache_iter(svn_boolean_t * completed,void * cache_void,svn_iter_apr_hash_cb_t user_cb,void * user_baton,apr_pool_t * scratch_pool)3029 svn_membuffer_cache_iter(svn_boolean_t *completed,
3030 void *cache_void,
3031 svn_iter_apr_hash_cb_t user_cb,
3032 void *user_baton,
3033 apr_pool_t *scratch_pool)
3034 {
3035 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3036 _("Can't iterate a membuffer-based cache"));
3037 }
3038
3039 /* Implement svn_cache__vtable_t.get_partial (not thread-safe)
3040 */
3041 static svn_error_t *
svn_membuffer_cache_get_partial(void ** value_p,svn_boolean_t * found,void * cache_void,const void * key,svn_cache__partial_getter_func_t func,void * baton,apr_pool_t * result_pool)3042 svn_membuffer_cache_get_partial(void **value_p,
3043 svn_boolean_t *found,
3044 void *cache_void,
3045 const void *key,
3046 svn_cache__partial_getter_func_t func,
3047 void *baton,
3048 apr_pool_t *result_pool)
3049 {
3050 svn_membuffer_cache_t *cache = cache_void;
3051
3052 DEBUG_CACHE_MEMBUFFER_INIT_TAG(result_pool)
3053
3054 if (key == NULL)
3055 {
3056 *value_p = NULL;
3057 *found = FALSE;
3058
3059 return SVN_NO_ERROR;
3060 }
3061
3062 combine_key(cache, key, cache->key_len);
3063 SVN_ERR(membuffer_cache_get_partial(cache->membuffer,
3064 &cache->combined_key,
3065 value_p,
3066 found,
3067 func,
3068 baton,
3069 DEBUG_CACHE_MEMBUFFER_TAG
3070 result_pool));
3071
3072 return SVN_NO_ERROR;
3073 }
3074
3075 /* Implement svn_cache__vtable_t.set_partial (not thread-safe)
3076 */
3077 static svn_error_t *
svn_membuffer_cache_set_partial(void * cache_void,const void * key,svn_cache__partial_setter_func_t func,void * baton,apr_pool_t * scratch_pool)3078 svn_membuffer_cache_set_partial(void *cache_void,
3079 const void *key,
3080 svn_cache__partial_setter_func_t func,
3081 void *baton,
3082 apr_pool_t *scratch_pool)
3083 {
3084 svn_membuffer_cache_t *cache = cache_void;
3085
3086 DEBUG_CACHE_MEMBUFFER_INIT_TAG(scratch_pool)
3087
3088 if (key != NULL)
3089 {
3090 combine_key(cache, key, cache->key_len);
3091 SVN_ERR(membuffer_cache_set_partial(cache->membuffer,
3092 &cache->combined_key,
3093 func,
3094 baton,
3095 DEBUG_CACHE_MEMBUFFER_TAG
3096 scratch_pool));
3097 }
3098 return SVN_NO_ERROR;
3099 }
3100
3101 /* Implement svn_cache__vtable_t.is_cachable
3102 * (thread-safe even without mutex)
3103 */
3104 static svn_boolean_t
svn_membuffer_cache_is_cachable(void * cache_void,apr_size_t size)3105 svn_membuffer_cache_is_cachable(void *cache_void, apr_size_t size)
3106 {
3107 /* Don't allow extremely large element sizes. Otherwise, the cache
3108 * might by thrashed by a few extremely large entries. And the size
3109 * must be small enough to be stored in a 32 bit value.
3110 */
3111 svn_membuffer_cache_t *cache = cache_void;
3112 return cache->priority > SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY
3113 ? cache->membuffer->l2.size >= size && MAX_ITEM_SIZE >= size
3114 : size <= cache->membuffer->max_entry_size;
3115 }
3116
3117 /* Add statistics of SEGMENT to INFO. If INCLUDE_HISTOGRAM is TRUE,
3118 * accumulate index bucket fill levels in INFO->HISTOGRAM.
3119 */
3120 static svn_error_t *
svn_membuffer_get_segment_info(svn_membuffer_t * segment,svn_cache__info_t * info,svn_boolean_t include_histogram)3121 svn_membuffer_get_segment_info(svn_membuffer_t *segment,
3122 svn_cache__info_t *info,
3123 svn_boolean_t include_histogram)
3124 {
3125 apr_uint32_t i;
3126
3127 info->data_size += segment->l1.size + segment->l2.size;
3128 info->used_size += segment->data_used;
3129 info->total_size += segment->l1.size + segment->l2.size +
3130 segment->group_count * GROUP_SIZE * sizeof(entry_t);
3131
3132 info->used_entries += segment->used_entries;
3133 info->total_entries += segment->group_count * GROUP_SIZE;
3134
3135 if (include_histogram)
3136 for (i = 0; i < segment->group_count; ++i)
3137 if (is_group_initialized(segment, i))
3138 {
3139 entry_group_t *chain_end
3140 = last_group_in_chain(segment, &segment->directory[i]);
3141 apr_size_t use
3142 = MIN(chain_end->header.used,
3143 sizeof(info->histogram) / sizeof(info->histogram[0]) - 1);
3144 info->histogram[use]++;
3145 }
3146
3147 return SVN_NO_ERROR;
3148 }
3149
3150 /* Implement svn_cache__vtable_t.get_info
3151 * (thread-safe even without mutex)
3152 */
3153 static svn_error_t *
svn_membuffer_cache_get_info(void * cache_void,svn_cache__info_t * info,svn_boolean_t reset,apr_pool_t * result_pool)3154 svn_membuffer_cache_get_info(void *cache_void,
3155 svn_cache__info_t *info,
3156 svn_boolean_t reset,
3157 apr_pool_t *result_pool)
3158 {
3159 svn_membuffer_cache_t *cache = cache_void;
3160 apr_uint32_t i;
3161
3162 /* cache front-end specific data */
3163
3164 info->id = apr_pstrdup(result_pool, get_prefix_key(cache));
3165
3166 /* collect info from shared cache back-end */
3167
3168 for (i = 0; i < cache->membuffer->segment_count; ++i)
3169 {
3170 svn_membuffer_t *segment = cache->membuffer + i;
3171 WITH_READ_LOCK(segment,
3172 svn_membuffer_get_segment_info(segment, info, FALSE));
3173 }
3174
3175 return SVN_NO_ERROR;
3176 }
3177
3178
3179 /* the v-table for membuffer-based caches (single-threaded access)
3180 */
3181 static svn_cache__vtable_t membuffer_cache_vtable = {
3182 svn_membuffer_cache_get,
3183 svn_membuffer_cache_has_key,
3184 svn_membuffer_cache_set,
3185 svn_membuffer_cache_iter,
3186 svn_membuffer_cache_is_cachable,
3187 svn_membuffer_cache_get_partial,
3188 svn_membuffer_cache_set_partial,
3189 svn_membuffer_cache_get_info
3190 };
3191
3192 /* Implement svn_cache__vtable_t.get and serialize all cache access.
3193 */
3194 static svn_error_t *
svn_membuffer_cache_get_synced(void ** value_p,svn_boolean_t * found,void * cache_void,const void * key,apr_pool_t * result_pool)3195 svn_membuffer_cache_get_synced(void **value_p,
3196 svn_boolean_t *found,
3197 void *cache_void,
3198 const void *key,
3199 apr_pool_t *result_pool)
3200 {
3201 svn_membuffer_cache_t *cache = cache_void;
3202 SVN_MUTEX__WITH_LOCK(cache->mutex,
3203 svn_membuffer_cache_get(value_p,
3204 found,
3205 cache_void,
3206 key,
3207 result_pool));
3208
3209 return SVN_NO_ERROR;
3210 }
3211
3212 /* Implement svn_cache__vtable_t.has_key and serialize all cache access.
3213 */
3214 static svn_error_t *
svn_membuffer_cache_has_key_synced(svn_boolean_t * found,void * cache_void,const void * key,apr_pool_t * result_pool)3215 svn_membuffer_cache_has_key_synced(svn_boolean_t *found,
3216 void *cache_void,
3217 const void *key,
3218 apr_pool_t *result_pool)
3219 {
3220 svn_membuffer_cache_t *cache = cache_void;
3221 SVN_MUTEX__WITH_LOCK(cache->mutex,
3222 svn_membuffer_cache_has_key(found,
3223 cache_void,
3224 key,
3225 result_pool));
3226
3227 return SVN_NO_ERROR;
3228 }
3229
3230 /* Implement svn_cache__vtable_t.set and serialize all cache access.
3231 */
3232 static svn_error_t *
svn_membuffer_cache_set_synced(void * cache_void,const void * key,void * value,apr_pool_t * scratch_pool)3233 svn_membuffer_cache_set_synced(void *cache_void,
3234 const void *key,
3235 void *value,
3236 apr_pool_t *scratch_pool)
3237 {
3238 svn_membuffer_cache_t *cache = cache_void;
3239 SVN_MUTEX__WITH_LOCK(cache->mutex,
3240 svn_membuffer_cache_set(cache_void,
3241 key,
3242 value,
3243 scratch_pool));
3244
3245 return SVN_NO_ERROR;
3246 }
3247
3248 /* Implement svn_cache__vtable_t.get_partial and serialize all cache access.
3249 */
3250 static svn_error_t *
svn_membuffer_cache_get_partial_synced(void ** value_p,svn_boolean_t * found,void * cache_void,const void * key,svn_cache__partial_getter_func_t func,void * baton,apr_pool_t * result_pool)3251 svn_membuffer_cache_get_partial_synced(void **value_p,
3252 svn_boolean_t *found,
3253 void *cache_void,
3254 const void *key,
3255 svn_cache__partial_getter_func_t func,
3256 void *baton,
3257 apr_pool_t *result_pool)
3258 {
3259 svn_membuffer_cache_t *cache = cache_void;
3260 SVN_MUTEX__WITH_LOCK(cache->mutex,
3261 svn_membuffer_cache_get_partial(value_p,
3262 found,
3263 cache_void,
3264 key,
3265 func,
3266 baton,
3267 result_pool));
3268
3269 return SVN_NO_ERROR;
3270 }
3271
3272 /* Implement svn_cache__vtable_t.set_partial and serialize all cache access.
3273 */
3274 static svn_error_t *
svn_membuffer_cache_set_partial_synced(void * cache_void,const void * key,svn_cache__partial_setter_func_t func,void * baton,apr_pool_t * scratch_pool)3275 svn_membuffer_cache_set_partial_synced(void *cache_void,
3276 const void *key,
3277 svn_cache__partial_setter_func_t func,
3278 void *baton,
3279 apr_pool_t *scratch_pool)
3280 {
3281 svn_membuffer_cache_t *cache = cache_void;
3282 SVN_MUTEX__WITH_LOCK(cache->mutex,
3283 svn_membuffer_cache_set_partial(cache_void,
3284 key,
3285 func,
3286 baton,
3287 scratch_pool));
3288
3289 return SVN_NO_ERROR;
3290 }
3291
3292 /* the v-table for membuffer-based caches with multi-threading support)
3293 */
3294 static svn_cache__vtable_t membuffer_cache_synced_vtable = {
3295 svn_membuffer_cache_get_synced,
3296 svn_membuffer_cache_has_key_synced,
3297 svn_membuffer_cache_set_synced,
3298 svn_membuffer_cache_iter, /* no sync required */
3299 svn_membuffer_cache_is_cachable, /* no sync required */
3300 svn_membuffer_cache_get_partial_synced,
3301 svn_membuffer_cache_set_partial_synced,
3302 svn_membuffer_cache_get_info /* no sync required */
3303 };
3304
3305 /* standard serialization function for svn_stringbuf_t items.
3306 * Implements svn_cache__serialize_func_t.
3307 */
3308 static svn_error_t *
serialize_svn_stringbuf(void ** buffer,apr_size_t * buffer_size,void * item,apr_pool_t * result_pool)3309 serialize_svn_stringbuf(void **buffer,
3310 apr_size_t *buffer_size,
3311 void *item,
3312 apr_pool_t *result_pool)
3313 {
3314 svn_stringbuf_t *value_str = item;
3315
3316 *buffer = value_str->data;
3317 *buffer_size = value_str->len + 1;
3318
3319 return SVN_NO_ERROR;
3320 }
3321
3322 /* standard de-serialization function for svn_stringbuf_t items.
3323 * Implements svn_cache__deserialize_func_t.
3324 */
3325 static svn_error_t *
deserialize_svn_stringbuf(void ** item,void * buffer,apr_size_t buffer_size,apr_pool_t * result_pool)3326 deserialize_svn_stringbuf(void **item,
3327 void *buffer,
3328 apr_size_t buffer_size,
3329 apr_pool_t *result_pool)
3330 {
3331 svn_stringbuf_t *value_str = apr_palloc(result_pool, sizeof(svn_stringbuf_t));
3332
3333 value_str->pool = result_pool;
3334 value_str->blocksize = buffer_size;
3335 value_str->data = buffer;
3336 value_str->len = buffer_size-1;
3337 *item = value_str;
3338
3339 return SVN_NO_ERROR;
3340 }
3341
3342 /* Construct a svn_cache__t object on top of a shared memcache.
3343 */
3344 svn_error_t *
svn_cache__create_membuffer_cache(svn_cache__t ** cache_p,svn_membuffer_t * membuffer,svn_cache__serialize_func_t serializer,svn_cache__deserialize_func_t deserializer,apr_ssize_t klen,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)3345 svn_cache__create_membuffer_cache(svn_cache__t **cache_p,
3346 svn_membuffer_t *membuffer,
3347 svn_cache__serialize_func_t serializer,
3348 svn_cache__deserialize_func_t deserializer,
3349 apr_ssize_t klen,
3350 const char *prefix,
3351 apr_uint32_t priority,
3352 svn_boolean_t thread_safe,
3353 svn_boolean_t short_lived,
3354 apr_pool_t *result_pool,
3355 apr_pool_t *scratch_pool)
3356 {
3357 svn_checksum_t *checksum;
3358 apr_size_t prefix_len, prefix_orig_len;
3359
3360 /* allocate the cache header structures
3361 */
3362 svn_cache__t *wrapper = apr_pcalloc(result_pool, sizeof(*wrapper));
3363 svn_membuffer_cache_t *cache = apr_pcalloc(result_pool, sizeof(*cache));
3364
3365 /* initialize our internal cache header
3366 */
3367 cache->membuffer = membuffer;
3368 cache->serializer = serializer
3369 ? serializer
3370 : serialize_svn_stringbuf;
3371 cache->deserializer = deserializer
3372 ? deserializer
3373 : deserialize_svn_stringbuf;
3374 cache->priority = priority;
3375 cache->key_len = klen;
3376
3377 SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, result_pool));
3378
3379 /* Copy the prefix into the prefix full key. Align it to ITEM_ALIGMENT.
3380 * Don't forget to include the terminating NUL. */
3381 prefix_orig_len = strlen(prefix) + 1;
3382 prefix_len = ALIGN_VALUE(prefix_orig_len);
3383
3384 /* Paranoia check to ensure pointer arithmetics work as expected. */
3385 if (prefix_orig_len >= SVN_MAX_OBJECT_SIZE)
3386 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3387 _("Prefix too long"));
3388
3389 /* Construct the folded prefix key. */
3390 SVN_ERR(svn_checksum(&checksum,
3391 svn_checksum_md5,
3392 prefix,
3393 strlen(prefix),
3394 scratch_pool));
3395 memcpy(cache->prefix.fingerprint, checksum->digest,
3396 sizeof(cache->prefix.fingerprint));
3397 cache->prefix.key_len = prefix_len;
3398
3399 /* Fix-length keys of up to 16 bytes may be handled without storing the
3400 * full key separately for each item. */
3401 if ( (klen != APR_HASH_KEY_STRING)
3402 && (klen <= sizeof(cache->combined_key.entry_key.fingerprint))
3403 && !short_lived)
3404 SVN_ERR(prefix_pool_get(&cache->prefix.prefix_idx,
3405 membuffer->prefix_pool,
3406 prefix));
3407 else
3408 cache->prefix.prefix_idx = NO_INDEX;
3409
3410 /* If key combining is not guaranteed to produce unique results, we have
3411 * to handle full keys. Otherwise, leave it NULL. */
3412 if (cache->prefix.prefix_idx == NO_INDEX)
3413 {
3414 /* Initialize the combined key. Pre-allocate some extra room in the
3415 * full key such that we probably don't need to re-alloc. */
3416 cache->combined_key.entry_key = cache->prefix;
3417 svn_membuf__create(&cache->combined_key.full_key, prefix_len + 200,
3418 result_pool);
3419 memcpy((char *)cache->combined_key.full_key.data, prefix,
3420 prefix_orig_len);
3421 memset((char *)cache->combined_key.full_key.data + prefix_orig_len, 0,
3422 prefix_len - prefix_orig_len);
3423 }
3424 else
3425 {
3426 /* Initialize the combined key. We will never have the full combined
3427 * key, so leave it NULL and set its length to 0 to prevent access to
3428 * it. Keep the fingerprint 0 as well b/c it will always be set anew
3429 * by combine_key(). */
3430 cache->combined_key.entry_key.prefix_idx = cache->prefix.prefix_idx;
3431 cache->combined_key.entry_key.key_len = 0;
3432 }
3433
3434 /* initialize the generic cache wrapper
3435 */
3436 wrapper->vtable = thread_safe ? &membuffer_cache_synced_vtable
3437 : &membuffer_cache_vtable;
3438 wrapper->cache_internal = cache;
3439 wrapper->error_handler = 0;
3440 wrapper->error_baton = 0;
3441 wrapper->pretend_empty = !!getenv("SVN_X_DOES_NOT_MARK_THE_SPOT");
3442
3443 *cache_p = wrapper;
3444 return SVN_NO_ERROR;
3445 }
3446
3447 static svn_error_t *
svn_membuffer_get_global_segment_info(svn_membuffer_t * segment,svn_cache__info_t * info)3448 svn_membuffer_get_global_segment_info(svn_membuffer_t *segment,
3449 svn_cache__info_t *info)
3450 {
3451 info->gets += segment->total_reads;
3452 info->sets += segment->total_writes;
3453 info->hits += segment->total_hits;
3454
3455 WITH_READ_LOCK(segment,
3456 svn_membuffer_get_segment_info(segment, info, TRUE));
3457
3458 return SVN_NO_ERROR;
3459 }
3460
3461 svn_cache__info_t *
svn_cache__membuffer_get_global_info(apr_pool_t * pool)3462 svn_cache__membuffer_get_global_info(apr_pool_t *pool)
3463 {
3464 apr_uint32_t i;
3465
3466 svn_membuffer_t *membuffer = svn_cache__get_global_membuffer_cache();
3467 svn_cache__info_t *info = apr_pcalloc(pool, sizeof(*info));
3468
3469 /* cache front-end specific data */
3470
3471 info->id = "membuffer globals";
3472
3473 /* collect info from shared cache back-end */
3474
3475 for (i = 0; i < membuffer->segment_count; ++i)
3476 svn_error_clear(svn_membuffer_get_global_segment_info(membuffer + i,
3477 info));
3478
3479 return info;
3480 }
3481