]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_subr/cache-membuffer.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.git] / contrib / subversion / subversion / libsvn_subr / cache-membuffer.c
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 "md5.h"
31 #include "svn_private_config.h"
32 #include "cache.h"
33 #include "svn_string.h"
34 #include "private/svn_dep_compat.h"
35 #include "private/svn_mutex.h"
36 #include "private/svn_pseudo_md5.h"
37
38 /*
39  * This svn_cache__t implementation actually consists of two parts:
40  * a shared (per-process) singleton membuffer cache instance and shallow
41  * svn_cache__t front-end instances that each use different key spaces.
42  * For data management, they all forward to the singleton membuffer cache.
43  *
44  * A membuffer cache consists of two parts:
45  *
46  * 1. A linear data buffer containing cached items in a serialized
47  *    representation. There may be arbitrary gaps between entries.
48  * 2. A directory of cache entries. This is organized similar to CPU
49  *    data caches: for every possible key, there is exactly one group
50  *    of entries that may contain the header info for an item with
51  *    that given key. The result is a GROUP_SIZE-way associative cache.
52  *
53  * Only the start address of these two data parts are given as a native
54  * pointer. All other references are expressed as offsets to these pointers.
55  * With that design, it is relatively easy to share the same data structure
56  * between different processes and / or to persist them on disk. These
57  * out-of-process features have not been implemented, yet.
58  *
59  * The data buffer usage information is implicitly given by the directory
60  * entries. Every USED entry has a reference to the previous and the next
61  * used dictionary entry and this double-linked list is ordered by the
62  * offsets of their item data within the data buffer. So removing data,
63  * for instance, is done simply by unlinking it from the chain, implicitly
64  * marking the entry as well as the data buffer section previously
65  * associated to it as unused.
66  *
67  * Insertion can occur at only one, sliding position. It is marked by its
68  * offset in the data buffer plus the index of the first used entry at or
69  * behind that position. If this gap is too small to accommodate the new
70  * item, the insertion window is extended as described below. The new entry
71  * will always be inserted at the bottom end of the window and since the
72  * next used entry is known, properly sorted insertion is possible.
73  *
74  * To make the cache perform robustly in a wide range of usage scenarios,
75  * a randomized variant of LFU is used (see ensure_data_insertable for
76  * details). Every item holds a read hit counter and there is a global read
77  * hit counter. The more hits an entry has in relation to the average, the
78  * more it is likely to be kept using a rand()-based condition. The test is
79  * applied only to the entry following the insertion window. If it doesn't
80  * get evicted, it is moved to the begin of that window and the window is
81  * moved.
82  *
83  * Moreover, the entry's hits get halved to make that entry more likely to
84  * be removed the next time the sliding insertion / removal window comes by.
85  * As a result, frequently used entries are likely not to be dropped until
86  * they get not used for a while. Also, even a cache thrashing situation
87  * about 50% of the content survives every 50% of the cache being re-written
88  * with new entries. For details on the fine-tuning involved, see the
89  * comments in ensure_data_insertable().
90  *
91  * To limit the entry size and management overhead, not the actual item keys
92  * but only their MD5 checksums will not be stored. This is reasonably safe
93  * to do since users have only limited control over the full keys, even if
94  * these contain folder paths. So, it is very hard to deliberately construct
95  * colliding keys. Random checksum collisions can be shown to be extremely
96  * unlikely.
97  *
98  * All access to the cached data needs to be serialized. Because we want
99  * to scale well despite that bottleneck, we simply segment the cache into
100  * a number of independent caches (segments). Items will be multiplexed based
101  * on their hash key.
102  */
103
104 /* APR's read-write lock implementation on Windows is horribly inefficient.
105  * Even with very low contention a runtime overhead of 35% percent has been
106  * measured for 'svn-bench null-export' over ra_serf.
107  *
108  * Use a simple mutex on Windows.  Because there is one mutex per segment,
109  * large machines should (and usually can) be configured with large caches
110  * such that read contention is kept low.  This is basically the situation
111  * we head before 1.8.
112  */
113 #ifdef WIN32
114 #  define USE_SIMPLE_MUTEX 1
115 #else
116 #  define USE_SIMPLE_MUTEX 0
117 #endif
118
119 /* A 16-way associative cache seems to be a good compromise between
120  * performance (worst-case lookups) and efficiency-loss due to collisions.
121  *
122  * This value may be changed to any positive integer.
123  */
124 #define GROUP_SIZE 16
125
126 /* For more efficient copy operations, let's align all data items properly.
127  * Must be a power of 2.
128  */
129 #define ITEM_ALIGNMENT 16
130
131 /* By default, don't create cache segments smaller than this value unless
132  * the total cache size itself is smaller.
133  */
134 #define DEFAULT_MIN_SEGMENT_SIZE APR_UINT64_C(0x2000000)
135
136 /* The minimum segment size we will allow for multi-segmented caches
137  */
138 #define MIN_SEGMENT_SIZE APR_UINT64_C(0x10000)
139
140 /* The maximum number of segments allowed. Larger numbers reduce the size
141  * of each segment, in turn reducing the max size of a cachable item.
142  * Also, each segment gets its own lock object. The actual number supported
143  * by the OS may therefore be lower and svn_cache__membuffer_cache_create
144  * may return an error.
145  */
146 #define MAX_SEGMENT_COUNT 0x10000
147
148 /* As of today, APR won't allocate chunks of 4GB or more. So, limit the
149  * segment size to slightly below that.
150  */
151 #define MAX_SEGMENT_SIZE APR_UINT64_C(0xffff0000)
152
153 /* We don't mark the initialization status for every group but initialize
154  * a number of groups at once. That will allow for a very small init flags
155  * vector that is likely to fit into the CPU caches even for fairly large
156  * membuffer caches. For instance, the default of 32 means 8x32 groups per
157  * byte, i.e. 8 flags/byte x 32 groups/flag x 8 entries/group x 40 index
158  * bytes/entry x 8 cache bytes/index byte = 1kB init vector / 640MB cache.
159  */
160 #define GROUP_INIT_GRANULARITY 32
161
162 /* Invalid index reference value. Equivalent to APR_UINT32_T(-1)
163  */
164 #define NO_INDEX APR_UINT32_MAX
165
166 /* To save space in our group structure, we only use 32 bit size values
167  * and, therefore, limit the size of each entry to just below 4GB.
168  * Supporting larger items is not a good idea as the data transfer
169  * to and from the cache would block other threads for a very long time.
170  */
171 #define MAX_ITEM_SIZE ((apr_uint32_t)(0 - ITEM_ALIGNMENT))
172
173 /* A 16 byte key type. We use that to identify cache entries.
174  * The notation as just two integer values will cause many compilers
175  * to create better code.
176  */
177 typedef apr_uint64_t entry_key_t[2];
178
179 /* Debugging / corruption detection support.
180  * If you define this macro, the getter functions will performed expensive
181  * checks on the item data, requested keys and entry types. If there is
182  * a mismatch found in any of them when being compared with the values
183  * remembered in the setter function, an error will be returned.
184  */
185 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
186
187 /* The prefix passed to svn_cache__create_membuffer_cache() effectively
188  * defines the type of all items stored by that cache instance. We'll take
189  * the last 7 bytes + \0 as plaintext for easy identification by the dev.
190  */
191 #define PREFIX_TAIL_LEN 8
192
193 /* This record will be attached to any cache entry. It tracks item data
194  * (content), key and type as hash values and is the baseline against which
195  * the getters will compare their results to detect inconsistencies.
196  */
197 typedef struct entry_tag_t
198 {
199   /* MD5 checksum over the serialized the item data.
200    */
201   unsigned char content_hash [APR_MD5_DIGESTSIZE];
202
203   /* Hash value of the svn_cache_t instance that wrote the item
204    * (i.e. a combination of type and repository)
205    */
206   unsigned char prefix_hash [APR_MD5_DIGESTSIZE];
207
208   /* Note that this only covers the variable part of the key,
209    * i.e. it will be different from the full key hash used for
210    * cache indexing.
211    */
212   unsigned char key_hash [APR_MD5_DIGESTSIZE];
213
214   /* Last letters from of the key in human readable format
215    * (ends with the type identifier, e.g. "DAG")
216    */
217   char prefix_tail[PREFIX_TAIL_LEN];
218
219   /* Length of the variable key part.
220    */
221   apr_size_t key_len;
222
223 } entry_tag_t;
224
225 /* Per svn_cache_t instance initialization helper.
226  */
227 static void get_prefix_tail(const char *prefix, char *prefix_tail)
228 {
229   apr_size_t len = strlen(prefix);
230   apr_size_t to_copy = len > PREFIX_TAIL_LEN-1 ? PREFIX_TAIL_LEN-1 : len;
231
232   memset(prefix_tail, 0, PREFIX_TAIL_LEN);
233   memcpy(prefix_tail, prefix + len - to_copy, to_copy);
234 }
235
236 /* Initialize all members of TAG except for the content hash.
237  */
238 static svn_error_t *store_key_part(entry_tag_t *tag,
239                                    entry_key_t prefix_hash,
240                                    char *prefix_tail,
241                                    const void *key,
242                                    apr_size_t key_len,
243                                    apr_pool_t *pool)
244 {
245   svn_checksum_t *checksum;
246   SVN_ERR(svn_checksum(&checksum,
247                        svn_checksum_md5,
248                        key,
249                        key_len,
250                        pool));
251
252   memcpy(tag->prefix_hash, prefix_hash, sizeof(tag->prefix_hash));
253   memcpy(tag->key_hash, checksum->digest, sizeof(tag->key_hash));
254   memcpy(tag->prefix_tail, prefix_tail, sizeof(tag->prefix_tail));
255
256   tag->key_len = key_len;
257
258   return SVN_NO_ERROR;
259 }
260
261 /* Initialize the content hash member of TAG.
262  */
263 static svn_error_t* store_content_part(entry_tag_t *tag,
264                                        const char *data,
265                                        apr_size_t size,
266                                        apr_pool_t *pool)
267 {
268   svn_checksum_t *checksum;
269   SVN_ERR(svn_checksum(&checksum,
270                        svn_checksum_md5,
271                        data,
272                        size,
273                        pool));
274
275   memcpy(tag->content_hash, checksum->digest, sizeof(tag->content_hash));
276
277   return SVN_NO_ERROR;
278 }
279
280 /* Compare two tags and fail with an assertion upon differences.
281  */
282 static svn_error_t* assert_equal_tags(const entry_tag_t *lhs,
283                                       const entry_tag_t *rhs)
284 {
285   SVN_ERR_ASSERT(memcmp(lhs->content_hash, rhs->content_hash,
286                         sizeof(lhs->content_hash)) == 0);
287   SVN_ERR_ASSERT(memcmp(lhs->prefix_hash, rhs->prefix_hash,
288                         sizeof(lhs->prefix_hash)) == 0);
289   SVN_ERR_ASSERT(memcmp(lhs->key_hash, rhs->key_hash,
290                         sizeof(lhs->key_hash)) == 0);
291   SVN_ERR_ASSERT(memcmp(lhs->prefix_tail, rhs->prefix_tail,
292                         sizeof(lhs->prefix_tail)) == 0);
293
294   SVN_ERR_ASSERT(lhs->key_len == rhs->key_len);
295
296   return SVN_NO_ERROR;
297 }
298
299 /* Reoccurring code snippets.
300  */
301
302 #define DEBUG_CACHE_MEMBUFFER_TAG_ARG entry_tag_t *tag,
303
304 #define DEBUG_CACHE_MEMBUFFER_TAG tag,
305
306 #define DEBUG_CACHE_MEMBUFFER_INIT_TAG                         \
307   entry_tag_t _tag;                                            \
308   entry_tag_t *tag = &_tag;                                    \
309   SVN_ERR(store_key_part(tag,                                  \
310                          cache->prefix,                        \
311                          cache->prefix_tail,                   \
312                          key,                                  \
313                          cache->key_len == APR_HASH_KEY_STRING \
314                              ? strlen((const char *) key)      \
315                              : cache->key_len,                 \
316                          cache->pool));
317
318 #else
319
320 /* Don't generate any checks if consistency checks have not been enabled.
321  */
322 #define DEBUG_CACHE_MEMBUFFER_TAG_ARG
323 #define DEBUG_CACHE_MEMBUFFER_TAG
324 #define DEBUG_CACHE_MEMBUFFER_INIT_TAG
325
326 #endif /* SVN_DEBUG_CACHE_MEMBUFFER */
327
328 /* A single dictionary entry. Since all entries will be allocated once
329  * during cache creation, those entries might be either used or unused.
330  * An entry is used if and only if it is contained in the doubly-linked
331  * list of used entries.
332  */
333 typedef struct entry_t
334 {
335   /* Identifying the data item. Only valid for used entries.
336    */
337   entry_key_t key;
338
339   /* The offset of the cached item's serialized data within the data buffer.
340    */
341   apr_uint64_t offset;
342
343   /* Size of the serialized item data. May be 0.
344    * Only valid for used entries.
345    */
346   apr_size_t size;
347
348   /* Number of (read) hits for this entry. Will be reset upon write.
349    * Only valid for used entries.
350    */
351   apr_uint32_t hit_count;
352
353   /* Reference to the next used entry in the order defined by offset.
354    * NO_INDEX indicates the end of the list; this entry must be referenced
355    * by the caches membuffer_cache_t.last member. NO_INDEX also implies
356    * that the data buffer is not used beyond offset+size.
357    * Only valid for used entries.
358    */
359   apr_uint32_t next;
360
361   /* Reference to the previous used entry in the order defined by offset.
362    * NO_INDEX indicates the end of the list; this entry must be referenced
363    * by the caches membuffer_cache_t.first member.
364    * Only valid for used entries.
365    */
366   apr_uint32_t previous;
367
368 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
369   /* Remember type, content and key hashes.
370    */
371   entry_tag_t tag;
372 #endif
373 } entry_t;
374
375 /* We group dictionary entries to make this GROUP-SIZE-way associative.
376  */
377 typedef struct entry_group_t
378 {
379   /* number of entries used [0 .. USED-1] */
380   apr_uint32_t used;
381
382   /* the actual entries */
383   entry_t entries[GROUP_SIZE];
384 } entry_group_t;
385
386 /* The cache header structure.
387  */
388 struct svn_membuffer_t
389 {
390   /* Number of cache segments. Must be a power of 2.
391      Please note that this structure represents only one such segment
392      and that all segments must / will report the same values here. */
393   apr_uint32_t segment_count;
394
395   /* The dictionary, GROUP_SIZE * group_count entries long. Never NULL.
396    */
397   entry_group_t *directory;
398
399   /* Flag array with group_count / GROUP_INIT_GRANULARITY _bit_ elements.
400    * Allows for efficiently marking groups as "not initialized".
401    */
402   unsigned char *group_initialized;
403
404   /* Size of dictionary in groups. Must be > 0.
405    */
406   apr_uint32_t group_count;
407
408   /* Reference to the first (defined by the order content in the data
409    * buffer) dictionary entry used by any data item.
410    * NO_INDEX for an empty cache.
411    */
412   apr_uint32_t first;
413
414   /* Reference to the last (defined by the order content in the data
415    * buffer) dictionary entry used by any data item.
416    * NO_INDEX for an empty cache.
417    */
418   apr_uint32_t last;
419
420   /* Reference to the first (defined by the order content in the data
421    * buffer) used dictionary entry behind the insertion position
422    * (current_data). If NO_INDEX, the data buffer is free starting at the
423    * current_data offset.
424    */
425   apr_uint32_t next;
426
427
428   /* Pointer to the data buffer, data_size bytes long. Never NULL.
429    */
430   unsigned char *data;
431
432   /* Size of data buffer in bytes. Must be > 0.
433    */
434   apr_uint64_t data_size;
435
436   /* Offset in the data buffer where the next insertion shall occur.
437    */
438   apr_uint64_t current_data;
439
440   /* Total number of data buffer bytes in use.
441    */
442   apr_uint64_t data_used;
443
444   /* Largest entry size that we would accept.  For total cache sizes
445    * less than 4TB (sic!), this is determined by the total cache size.
446    */
447   apr_uint64_t max_entry_size;
448
449
450   /* Number of used dictionary entries, i.e. number of cached items.
451    * In conjunction with hit_count, this is used calculate the average
452    * hit count as part of the randomized LFU algorithm.
453    */
454   apr_uint32_t used_entries;
455
456   /* Sum of (read) hit counts of all used dictionary entries.
457    * In conjunction used_entries used_entries, this is used calculate
458    * the average hit count as part of the randomized LFU algorithm.
459    */
460   apr_uint64_t hit_count;
461
462
463   /* Total number of calls to membuffer_cache_get.
464    * Purely statistical information that may be used for profiling.
465    */
466   apr_uint64_t total_reads;
467
468   /* Total number of calls to membuffer_cache_set.
469    * Purely statistical information that may be used for profiling.
470    */
471   apr_uint64_t total_writes;
472
473   /* Total number of hits since the cache's creation.
474    * Purely statistical information that may be used for profiling.
475    */
476   apr_uint64_t total_hits;
477
478 #if APR_HAS_THREADS
479   /* A lock for intra-process synchronization to the cache, or NULL if
480    * the cache's creator doesn't feel the cache needs to be
481    * thread-safe.
482    */
483 #  if USE_SIMPLE_MUTEX
484   svn_mutex__t *lock;
485 #  else
486   apr_thread_rwlock_t *lock;
487 #  endif
488
489   /* If set, write access will wait until they get exclusive access.
490    * Otherwise, they will become no-ops if the segment is currently
491    * read-locked.  Only used when LOCK is an r/w lock.
492    */
493   svn_boolean_t allow_blocking_writes;
494 #endif
495 };
496
497 /* Align integer VALUE to the next ITEM_ALIGNMENT boundary.
498  */
499 #define ALIGN_VALUE(value) (((value) + ITEM_ALIGNMENT-1) & -ITEM_ALIGNMENT)
500
501 /* Align POINTER value to the next ITEM_ALIGNMENT boundary.
502  */
503 #define ALIGN_POINTER(pointer) ((void*)ALIGN_VALUE((apr_size_t)(char*)(pointer)))
504
505 /* If locking is supported for CACHE, acquire a read lock for it.
506  */
507 static svn_error_t *
508 read_lock_cache(svn_membuffer_t *cache)
509 {
510 #if APR_HAS_THREADS
511 #  if USE_SIMPLE_MUTEX
512   return svn_mutex__lock(cache->lock);
513 #  else
514   if (cache->lock)
515   {
516     apr_status_t status = apr_thread_rwlock_rdlock(cache->lock);
517     if (status)
518       return svn_error_wrap_apr(status, _("Can't lock cache mutex"));
519   }
520 #  endif
521 #endif
522   return SVN_NO_ERROR;
523 }
524
525 /* If locking is supported for CACHE, acquire a write lock for it.
526  */
527 static svn_error_t *
528 write_lock_cache(svn_membuffer_t *cache, svn_boolean_t *success)
529 {
530 #if APR_HAS_THREADS
531 #  if USE_SIMPLE_MUTEX
532
533   return svn_mutex__lock(cache->lock);
534
535 #  else
536
537   if (cache->lock)
538     {
539       apr_status_t status;
540       if (cache->allow_blocking_writes)
541         {
542           status = apr_thread_rwlock_wrlock(cache->lock);
543         }
544       else
545         {
546           status = apr_thread_rwlock_trywrlock(cache->lock);
547           if (SVN_LOCK_IS_BUSY(status))
548             {
549               *success = FALSE;
550               status = APR_SUCCESS;
551             }
552         }
553
554       if (status)
555         return svn_error_wrap_apr(status,
556                                   _("Can't write-lock cache mutex"));
557     }
558
559 #  endif
560 #endif
561   return SVN_NO_ERROR;
562 }
563
564 /* If locking is supported for CACHE, acquire an unconditional write lock
565  * for it.
566  */
567 static svn_error_t *
568 force_write_lock_cache(svn_membuffer_t *cache)
569 {
570 #if APR_HAS_THREADS
571 #  if USE_SIMPLE_MUTEX
572
573   return svn_mutex__lock(cache->lock);
574
575 #  else
576
577   apr_status_t status = apr_thread_rwlock_wrlock(cache->lock);
578   if (status)
579     return svn_error_wrap_apr(status,
580                               _("Can't write-lock cache mutex"));
581
582 #  endif
583 #endif
584   return SVN_NO_ERROR;
585 }
586
587 /* If locking is supported for CACHE, release the current lock
588  * (read or write).
589  */
590 static svn_error_t *
591 unlock_cache(svn_membuffer_t *cache, svn_error_t *err)
592 {
593 #if APR_HAS_THREADS
594 #  if USE_SIMPLE_MUTEX
595
596   return svn_mutex__unlock(cache->lock, err);
597
598 #  else
599
600   if (cache->lock)
601   {
602     apr_status_t status = apr_thread_rwlock_unlock(cache->lock);
603     if (err)
604       return err;
605
606     if (status)
607       return svn_error_wrap_apr(status, _("Can't unlock cache mutex"));
608   }
609
610 #  endif
611 #endif
612   return err;
613 }
614
615 /* If supported, guard the execution of EXPR with a read lock to cache.
616  * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
617  */
618 #define WITH_READ_LOCK(cache, expr)         \
619 do {                                        \
620   SVN_ERR(read_lock_cache(cache));          \
621   SVN_ERR(unlock_cache(cache, (expr)));     \
622 } while (0)
623
624 /* If supported, guard the execution of EXPR with a write lock to cache.
625  * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
626  *
627  * The write lock process is complicated if we don't allow to wait for
628  * the lock: If we didn't get the lock, we may still need to remove an
629  * existing entry for the given key because that content is now stale.
630  * Once we discovered such an entry, we unconditionally do a blocking
631  * wait for the write lock.  In case no old content could be found, a
632  * failing lock attempt is simply a no-op and we exit the macro.
633  */
634 #define WITH_WRITE_LOCK(cache, expr)                            \
635 do {                                                            \
636   svn_boolean_t got_lock = TRUE;                                \
637   SVN_ERR(write_lock_cache(cache, &got_lock));                  \
638   if (!got_lock)                                                \
639     {                                                           \
640       svn_boolean_t exists;                                     \
641       SVN_ERR(entry_exists(cache, group_index, key, &exists));  \
642       if (exists)                                               \
643         SVN_ERR(force_write_lock_cache(cache));                 \
644       else                                                      \
645         break;                                                  \
646     }                                                           \
647   SVN_ERR(unlock_cache(cache, (expr)));                         \
648 } while (0)
649
650 /* Resolve a dictionary entry reference, i.e. return the entry
651  * for the given IDX.
652  */
653 static APR_INLINE entry_t *
654 get_entry(svn_membuffer_t *cache, apr_uint32_t idx)
655 {
656   return &cache->directory[idx / GROUP_SIZE].entries[idx % GROUP_SIZE];
657 }
658
659 /* Get the entry references for the given ENTRY.
660  */
661 static APR_INLINE apr_uint32_t
662 get_index(svn_membuffer_t *cache, entry_t *entry)
663 {
664   apr_size_t group_index
665     = ((char *)entry - (char *)cache->directory) / sizeof(entry_group_t);
666
667   return (apr_uint32_t)group_index * GROUP_SIZE
668        + (apr_uint32_t)(entry - cache->directory[group_index].entries);
669 }
670
671 /* Remove the used ENTRY from the CACHE, i.e. make it "unused".
672  * In contrast to insertion, removal is possible for any entry.
673  */
674 static void
675 drop_entry(svn_membuffer_t *cache, entry_t *entry)
676 {
677   /* the group that ENTRY belongs to plus a number of useful index values
678    */
679   apr_uint32_t idx = get_index(cache, entry);
680   apr_uint32_t group_index = idx / GROUP_SIZE;
681   entry_group_t *group = &cache->directory[group_index];
682   apr_uint32_t last_in_group = group_index * GROUP_SIZE + group->used - 1;
683
684   /* Only valid to be called for used entries.
685    */
686   assert(idx <= last_in_group);
687
688   /* update global cache usage counters
689    */
690   cache->used_entries--;
691   cache->hit_count -= entry->hit_count;
692   cache->data_used -= entry->size;
693
694   /* extend the insertion window, if the entry happens to border it
695    */
696   if (idx == cache->next)
697     cache->next = entry->next;
698   else
699     if (entry->next == cache->next)
700       {
701         /* insertion window starts right behind the entry to remove
702          */
703         if (entry->previous == NO_INDEX)
704           {
705             /* remove the first entry -> insertion may start at pos 0, now */
706             cache->current_data = 0;
707           }
708         else
709           {
710             /* insertion may start right behind the previous entry */
711             entry_t *previous = get_entry(cache, entry->previous);
712             cache->current_data = ALIGN_VALUE(  previous->offset
713                                               + previous->size);
714           }
715       }
716
717   /* unlink it from the chain of used entries
718    */
719   if (entry->previous == NO_INDEX)
720     cache->first = entry->next;
721   else
722     get_entry(cache, entry->previous)->next = entry->next;
723
724   if (entry->next == NO_INDEX)
725     cache->last = entry->previous;
726   else
727     get_entry(cache, entry->next)->previous = entry->previous;
728
729   /* Move last entry into hole (if the removed one is not the last used).
730    * We need to do this since all used entries are at the beginning of
731    * the group's entries array.
732    */
733   if (idx < last_in_group)
734     {
735       /* copy the last used entry to the removed entry's index
736        */
737       *entry = group->entries[group->used-1];
738
739       /* update foreign links to new index
740        */
741       if (last_in_group == cache->next)
742         cache->next = idx;
743
744       if (entry->previous == NO_INDEX)
745         cache->first = idx;
746       else
747         get_entry(cache, entry->previous)->next = idx;
748
749       if (entry->next == NO_INDEX)
750         cache->last = idx;
751       else
752         get_entry(cache, entry->next)->previous = idx;
753     }
754
755   /* Update the number of used entries.
756    */
757   group->used--;
758 }
759
760 /* Insert ENTRY into the chain of used dictionary entries. The entry's
761  * offset and size members must already have been initialized. Also,
762  * the offset must match the beginning of the insertion window.
763  */
764 static void
765 insert_entry(svn_membuffer_t *cache, entry_t *entry)
766 {
767   /* the group that ENTRY belongs to plus a number of useful index values
768    */
769   apr_uint32_t idx = get_index(cache, entry);
770   apr_uint32_t group_index = idx / GROUP_SIZE;
771   entry_group_t *group = &cache->directory[group_index];
772   entry_t *next = cache->next == NO_INDEX
773                 ? NULL
774                 : get_entry(cache, cache->next);
775
776   /* The entry must start at the beginning of the insertion window.
777    * It must also be the first unused entry in the group.
778    */
779   assert(entry->offset == cache->current_data);
780   assert(idx == group_index * GROUP_SIZE + group->used);
781   cache->current_data = ALIGN_VALUE(entry->offset + entry->size);
782
783   /* update usage counters
784    */
785   cache->used_entries++;
786   cache->data_used += entry->size;
787   entry->hit_count = 0;
788   group->used++;
789
790   /* update entry chain
791    */
792   entry->next = cache->next;
793   if (cache->first == NO_INDEX)
794     {
795       /* insert as the first entry and only in the chain
796        */
797       entry->previous = NO_INDEX;
798       cache->last = idx;
799       cache->first = idx;
800     }
801   else if (next == NULL)
802     {
803       /* insert as the last entry in the chain.
804        * Note that it cannot also be at the beginning of the chain.
805        */
806       entry->previous = cache->last;
807       get_entry(cache, cache->last)->next = idx;
808       cache->last = idx;
809     }
810   else
811     {
812       /* insert either at the start of a non-empty list or
813        * somewhere in the middle
814        */
815       entry->previous = next->previous;
816       next->previous = idx;
817
818       if (entry->previous != NO_INDEX)
819         get_entry(cache, entry->previous)->next = idx;
820       else
821         cache->first = idx;
822     }
823
824   /* The current insertion position must never point outside our
825    * data buffer.
826    */
827   assert(cache->current_data <= cache->data_size);
828 }
829
830 /* Map a KEY of 16 bytes to the CACHE and group that shall contain the
831  * respective item.
832  */
833 static apr_uint32_t
834 get_group_index(svn_membuffer_t **cache,
835                 entry_key_t key)
836 {
837   svn_membuffer_t *segment0 = *cache;
838
839   /* select the cache segment to use. they have all the same group_count */
840   *cache = &segment0[key[0] & (segment0->segment_count -1)];
841   return key[1] % segment0->group_count;
842 }
843
844 /* Reduce the hit count of ENTRY and update the accumulated hit info
845  * in CACHE accordingly.
846  */
847 static APR_INLINE void
848 let_entry_age(svn_membuffer_t *cache, entry_t *entry)
849 {
850   apr_uint32_t hits_removed = (entry->hit_count + 1) >> 1;
851
852   cache->hit_count -= hits_removed;
853   entry->hit_count -= hits_removed;
854 }
855
856 /* Returns 0 if the entry group identified by GROUP_INDEX in CACHE has not
857  * been initialized, yet. In that case, this group can not data. Otherwise,
858  * a non-zero value is returned.
859  */
860 static APR_INLINE unsigned char
861 is_group_initialized(svn_membuffer_t *cache, apr_uint32_t group_index)
862 {
863   unsigned char flags
864     = cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)];
865   unsigned char bit_mask
866     = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
867
868   return flags & bit_mask;
869 }
870
871 /* Initializes the section of the directory in CACHE that contains
872  * the entry group identified by GROUP_INDEX. */
873 static void
874 initialize_group(svn_membuffer_t *cache, apr_uint32_t group_index)
875 {
876   unsigned char bit_mask;
877   apr_uint32_t i;
878
879   /* range of groups to initialize due to GROUP_INIT_GRANULARITY */
880   apr_uint32_t first_index =
881       (group_index / GROUP_INIT_GRANULARITY) * GROUP_INIT_GRANULARITY;
882   apr_uint32_t last_index = first_index + GROUP_INIT_GRANULARITY;
883   if (last_index > cache->group_count)
884     last_index = cache->group_count;
885
886   for (i = first_index; i < last_index; ++i)
887     cache->directory[i].used = 0;
888
889   /* set the "initialized" bit for these groups */
890   bit_mask
891     = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
892   cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)]
893     |= bit_mask;
894 }
895
896 /* Given the GROUP_INDEX that shall contain an entry with the hash key
897  * TO_FIND, find that entry in the specified group.
898  *
899  * If FIND_EMPTY is not set, this function will return the one used entry
900  * that actually matches the hash or NULL, if no such entry exists.
901  *
902  * If FIND_EMPTY has been set, this function will drop the one used entry
903  * that actually matches the hash (i.e. make it fit to be replaced with
904  * new content), an unused entry or a forcibly removed entry (if all
905  * group entries are currently in use). The entries' hash value will be
906  * initialized with TO_FIND.
907  */
908 static entry_t *
909 find_entry(svn_membuffer_t *cache,
910            apr_uint32_t group_index,
911            const apr_uint64_t to_find[2],
912            svn_boolean_t find_empty)
913 {
914   entry_group_t *group;
915   entry_t *entry = NULL;
916   apr_size_t i;
917
918   /* get the group that *must* contain the entry
919    */
920   group = &cache->directory[group_index];
921
922   /* If the entry group has not been initialized, yet, there is no data.
923    */
924   if (! is_group_initialized(cache, group_index))
925     {
926       if (find_empty)
927         {
928           initialize_group(cache, group_index);
929           entry = &group->entries[0];
930
931           /* initialize entry for the new key */
932           entry->key[0] = to_find[0];
933           entry->key[1] = to_find[1];
934         }
935
936       return entry;
937     }
938
939   /* try to find the matching entry
940    */
941   for (i = 0; i < group->used; ++i)
942     if (   to_find[0] == group->entries[i].key[0]
943         && to_find[1] == group->entries[i].key[1])
944       {
945         /* found it
946          */
947         entry = &group->entries[i];
948         if (find_empty)
949           drop_entry(cache, entry);
950         else
951           return entry;
952       }
953
954   /* None found. Are we looking for a free entry?
955    */
956   if (find_empty)
957     {
958       /* if there is no empty entry, delete the oldest entry
959        */
960       if (group->used == GROUP_SIZE)
961         {
962           /* every entry gets the same chance of being removed.
963            * Otherwise, we free the first entry, fill it and
964            * remove it again on the next occasion without considering
965            * the other entries in this group.
966            */
967           entry = &group->entries[rand() % GROUP_SIZE];
968           for (i = 1; i < GROUP_SIZE; ++i)
969             if (entry->hit_count > group->entries[i].hit_count)
970               entry = &group->entries[i];
971
972           /* for the entries that don't have been removed,
973            * reduce their hit counts to put them at a relative
974            * disadvantage the next time.
975            */
976           for (i = 0; i < GROUP_SIZE; ++i)
977             if (entry != &group->entries[i])
978               let_entry_age(cache, entry);
979
980           drop_entry(cache, entry);
981         }
982
983       /* initialize entry for the new key
984        */
985       entry = &group->entries[group->used];
986       entry->key[0] = to_find[0];
987       entry->key[1] = to_find[1];
988     }
989
990   return entry;
991 }
992
993 /* Move a surviving ENTRY from just behind the insertion window to
994  * its beginning and move the insertion window up accordingly.
995  */
996 static void
997 move_entry(svn_membuffer_t *cache, entry_t *entry)
998 {
999   apr_size_t size = ALIGN_VALUE(entry->size);
1000
1001   /* This entry survived this cleansing run. Reset half of its
1002    * hit count so that its removal gets more likely in the next
1003    * run unless someone read / hit this entry in the meantime.
1004    */
1005   let_entry_age(cache, entry);
1006
1007   /* Move the entry to the start of the empty / insertion section
1008    * (if it isn't there already). Size-aligned moves are legal
1009    * since all offsets and block sizes share this same alignment.
1010    * Size-aligned moves tend to be faster than non-aligned ones
1011    * because no "odd" bytes at the end need to special treatment.
1012    */
1013   if (entry->offset != cache->current_data)
1014     {
1015       memmove(cache->data + cache->current_data,
1016               cache->data + entry->offset,
1017               size);
1018       entry->offset = cache->current_data;
1019     }
1020
1021   /* The insertion position is now directly behind this entry.
1022    */
1023   cache->current_data = entry->offset + size;
1024   cache->next = entry->next;
1025
1026   /* The current insertion position must never point outside our
1027    * data buffer.
1028    */
1029   assert(cache->current_data <= cache->data_size);
1030 }
1031
1032 /* If necessary, enlarge the insertion window until it is at least
1033  * SIZE bytes long. SIZE must not exceed the data buffer size.
1034  * Return TRUE if enough room could be found or made. A FALSE result
1035  * indicates that the respective item shall not be added.
1036  */
1037 static svn_boolean_t
1038 ensure_data_insertable(svn_membuffer_t *cache, apr_size_t size)
1039 {
1040   entry_t *entry;
1041   apr_uint64_t average_hit_value;
1042   apr_uint64_t threshold;
1043
1044   /* accumulated size of the entries that have been removed to make
1045    * room for the new one.
1046    */
1047   apr_size_t drop_size = 0;
1048
1049   /* This loop will eventually terminate because every cache entry
1050    * would get dropped eventually:
1051    * - hit counts become 0 after the got kept for 32 full scans
1052    * - larger elements get dropped as soon as their hit count is 0
1053    * - smaller and smaller elements get removed as the average
1054    *   entry size drops (average drops by a factor of 8 per scan)
1055    * - after no more than 43 full scans, all elements would be removed
1056    *
1057    * Since size is < 4th of the cache size and about 50% of all
1058    * entries get removed by a scan, it is very unlikely that more
1059    * than a fractional scan will be necessary.
1060    */
1061   while (1)
1062     {
1063       /* first offset behind the insertion window
1064        */
1065       apr_uint64_t end = cache->next == NO_INDEX
1066                        ? cache->data_size
1067                        : get_entry(cache, cache->next)->offset;
1068
1069       /* leave function as soon as the insertion window is large enough
1070        */
1071       if (end >= size + cache->current_data)
1072         return TRUE;
1073
1074       /* Don't be too eager to cache data. Smaller items will fit into
1075        * the cache after dropping a single item. Of the larger ones, we
1076        * will only accept about 50%. They are also likely to get evicted
1077        * soon due to their notoriously low hit counts.
1078        *
1079        * As long as enough similarly or even larger sized entries already
1080        * exist in the cache, much less insert requests will be rejected.
1081        */
1082       if (2 * drop_size > size)
1083         return FALSE;
1084
1085       /* try to enlarge the insertion window
1086        */
1087       if (cache->next == NO_INDEX)
1088         {
1089           /* We reached the end of the data buffer; restart at the beginning.
1090            * Due to the randomized nature of our LFU implementation, very
1091            * large data items may require multiple passes. Therefore, SIZE
1092            * should be restricted to significantly less than data_size.
1093            */
1094           cache->current_data = 0;
1095           cache->next = cache->first;
1096         }
1097       else
1098         {
1099           entry = get_entry(cache, cache->next);
1100
1101           /* Keep entries that are very small. Those are likely to be data
1102            * headers or similar management structures. So, they are probably
1103            * important while not occupying much space.
1104            * But keep them only as long as they are a minority.
1105            */
1106           if (   (apr_uint64_t)entry->size * cache->used_entries
1107                < cache->data_used / 8)
1108             {
1109               move_entry(cache, entry);
1110             }
1111           else
1112             {
1113               svn_boolean_t keep;
1114
1115               if (cache->hit_count > cache->used_entries)
1116                 {
1117                   /* Roll the dice and determine a threshold somewhere from 0 up
1118                    * to 2 times the average hit count.
1119                    */
1120                   average_hit_value = cache->hit_count / cache->used_entries;
1121                   threshold = (average_hit_value+1) * (rand() % 4096) / 2048;
1122
1123                   keep = entry->hit_count >= threshold;
1124                 }
1125               else
1126                 {
1127                   /* general hit count is low. Keep everything that got hit
1128                    * at all and assign some 50% survival chance to everything
1129                    * else.
1130                    */
1131                   keep = (entry->hit_count > 0) || (rand() & 1);
1132                 }
1133
1134               /* keepers or destroyers? */
1135               if (keep)
1136                 {
1137                   move_entry(cache, entry);
1138                 }
1139               else
1140                 {
1141                  /* Drop the entry from the end of the insertion window, if it
1142                   * has been hit less than the threshold. Otherwise, keep it and
1143                   * move the insertion window one entry further.
1144                   */
1145                   drop_size += entry->size;
1146                   drop_entry(cache, entry);
1147                 }
1148             }
1149         }
1150     }
1151
1152   /* This will never be reached. But if it was, "can't insert" was the
1153    * right answer. */
1154 }
1155
1156 /* Mimic apr_pcalloc in APR_POOL_DEBUG mode, i.e. handle failed allocations
1157  * (e.g. OOM) properly: Allocate at least SIZE bytes from POOL and zero
1158  * the content of the allocated memory if ZERO has been set. Return NULL
1159  * upon failed allocations.
1160  *
1161  * Also, satisfy our buffer alignment needs for performance reasons.
1162  */
1163 static void* secure_aligned_alloc(apr_pool_t *pool,
1164                                   apr_size_t size,
1165                                   svn_boolean_t zero)
1166 {
1167   void* memory = apr_palloc(pool, size + ITEM_ALIGNMENT);
1168   if (memory != NULL)
1169     {
1170       memory = ALIGN_POINTER(memory);
1171       if (zero)
1172         memset(memory, 0, size);
1173     }
1174
1175   return memory;
1176 }
1177
1178 svn_error_t *
1179 svn_cache__membuffer_cache_create(svn_membuffer_t **cache,
1180                                   apr_size_t total_size,
1181                                   apr_size_t directory_size,
1182                                   apr_size_t segment_count,
1183                                   svn_boolean_t thread_safe,
1184                                   svn_boolean_t allow_blocking_writes,
1185                                   apr_pool_t *pool)
1186 {
1187   svn_membuffer_t *c;
1188
1189   apr_uint32_t seg;
1190   apr_uint32_t group_count;
1191   apr_uint32_t group_init_size;
1192   apr_uint64_t data_size;
1193   apr_uint64_t max_entry_size;
1194
1195   /* Limit the total size (only relevant if we can address > 4GB)
1196    */
1197 #if APR_SIZEOF_VOIDP > 4
1198   if (total_size > MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT)
1199     total_size = MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT;
1200 #endif
1201
1202   /* Limit the segment count
1203    */
1204   if (segment_count > MAX_SEGMENT_COUNT)
1205     segment_count = MAX_SEGMENT_COUNT;
1206   if (segment_count * MIN_SEGMENT_SIZE > total_size)
1207     segment_count = total_size / MIN_SEGMENT_SIZE;
1208
1209   /* The segment count must be a power of two. Round it down as necessary.
1210    */
1211   while ((segment_count & (segment_count-1)) != 0)
1212     segment_count &= segment_count-1;
1213
1214   /* if the caller hasn't provided a reasonable segment count or the above
1215    * limitations set it to 0, derive one from the absolute cache size
1216    */
1217   if (segment_count < 1)
1218     {
1219       /* Determine a reasonable number of cache segments. Segmentation is
1220        * only useful for multi-threaded / multi-core servers as it reduces
1221        * lock contention on these systems.
1222        *
1223        * But on these systems, we can assume that ample memory has been
1224        * allocated to this cache. Smaller caches should not be segmented
1225        * as this severely limits the maximum size of cachable items.
1226        *
1227        * Segments should not be smaller than 32MB and max. cachable item
1228        * size should grow as fast as segmentation.
1229        */
1230
1231       apr_uint32_t segment_count_shift = 0;
1232       while (((2 * DEFAULT_MIN_SEGMENT_SIZE) << (2 * segment_count_shift))
1233              < total_size)
1234         ++segment_count_shift;
1235
1236       segment_count = (apr_size_t)1 << segment_count_shift;
1237     }
1238
1239   /* If we have an extremely large cache (>512 GB), the default segment
1240    * size may exceed the amount allocatable as one chunk. In that case,
1241    * increase segmentation until we are under the threshold.
1242    */
1243   while (   total_size / segment_count > MAX_SEGMENT_SIZE
1244          && segment_count < MAX_SEGMENT_COUNT)
1245     segment_count *= 2;
1246
1247   /* allocate cache as an array of segments / cache objects */
1248   c = apr_palloc(pool, segment_count * sizeof(*c));
1249
1250   /* Split total cache size into segments of equal size
1251    */
1252   total_size /= segment_count;
1253   directory_size /= segment_count;
1254
1255   /* prevent pathological conditions: ensure a certain minimum cache size
1256    */
1257   if (total_size < 2 * sizeof(entry_group_t))
1258     total_size = 2 * sizeof(entry_group_t);
1259
1260   /* adapt the dictionary size accordingly, if necessary:
1261    * It must hold at least one group and must not exceed the cache size.
1262    */
1263   if (directory_size > total_size - sizeof(entry_group_t))
1264     directory_size = total_size - sizeof(entry_group_t);
1265   if (directory_size < sizeof(entry_group_t))
1266     directory_size = sizeof(entry_group_t);
1267
1268   /* limit the data size to what we can address.
1269    * Note that this cannot overflow since all values are of size_t.
1270    * Also, make it a multiple of the item placement granularity to
1271    * prevent subtle overflows.
1272    */
1273   data_size = ALIGN_VALUE(total_size - directory_size + 1) - ITEM_ALIGNMENT;
1274
1275   /* For cache sizes > 4TB, individual cache segments will be larger
1276    * than 16GB allowing for >4GB entries.  But caching chunks larger
1277    * than 4GB is simply not supported.
1278    */
1279   max_entry_size = data_size / 4 > MAX_ITEM_SIZE
1280                  ? MAX_ITEM_SIZE
1281                  : data_size / 4;
1282
1283   /* to keep the entries small, we use 32 bit indexes only
1284    * -> we need to ensure that no more then 4G entries exist.
1285    *
1286    * Note, that this limit could only be exceeded in a very
1287    * theoretical setup with about 1EB of cache.
1288    */
1289   group_count = directory_size / sizeof(entry_group_t)
1290                     >= (APR_UINT32_MAX / GROUP_SIZE)
1291               ? (APR_UINT32_MAX / GROUP_SIZE) - 1
1292               : (apr_uint32_t)(directory_size / sizeof(entry_group_t));
1293
1294   group_init_size = 1 + group_count / (8 * GROUP_INIT_GRANULARITY);
1295   for (seg = 0; seg < segment_count; ++seg)
1296     {
1297       /* allocate buffers and initialize cache members
1298        */
1299       c[seg].segment_count = (apr_uint32_t)segment_count;
1300
1301       c[seg].group_count = group_count;
1302       c[seg].directory = apr_pcalloc(pool,
1303                                      group_count * sizeof(entry_group_t));
1304
1305       /* Allocate and initialize directory entries as "not initialized",
1306          hence "unused" */
1307       c[seg].group_initialized = apr_pcalloc(pool, group_init_size);
1308
1309       c[seg].first = NO_INDEX;
1310       c[seg].last = NO_INDEX;
1311       c[seg].next = NO_INDEX;
1312
1313       c[seg].data_size = data_size;
1314       c[seg].data = secure_aligned_alloc(pool, (apr_size_t)data_size, FALSE);
1315       c[seg].current_data = 0;
1316       c[seg].data_used = 0;
1317       c[seg].max_entry_size = max_entry_size;
1318
1319       c[seg].used_entries = 0;
1320       c[seg].hit_count = 0;
1321       c[seg].total_reads = 0;
1322       c[seg].total_writes = 0;
1323       c[seg].total_hits = 0;
1324
1325       /* were allocations successful?
1326        * If not, initialize a minimal cache structure.
1327        */
1328       if (c[seg].data == NULL || c[seg].directory == NULL)
1329         {
1330           /* We are OOM. There is no need to proceed with "half a cache".
1331            */
1332           return svn_error_wrap_apr(APR_ENOMEM, "OOM");
1333         }
1334
1335 #if APR_HAS_THREADS
1336       /* A lock for intra-process synchronization to the cache, or NULL if
1337        * the cache's creator doesn't feel the cache needs to be
1338        * thread-safe.
1339        */
1340 #  if USE_SIMPLE_MUTEX
1341
1342       SVN_ERR(svn_mutex__init(&c[seg].lock, thread_safe, pool));
1343
1344 #  else
1345
1346       c[seg].lock = NULL;
1347       if (thread_safe)
1348         {
1349           apr_status_t status =
1350               apr_thread_rwlock_create(&(c[seg].lock), pool);
1351           if (status)
1352             return svn_error_wrap_apr(status, _("Can't create cache mutex"));
1353         }
1354
1355 #  endif
1356
1357       /* Select the behavior of write operations.
1358        */
1359       c[seg].allow_blocking_writes = allow_blocking_writes;
1360 #endif
1361     }
1362
1363   /* done here
1364    */
1365   *cache = c;
1366   return SVN_NO_ERROR;
1367 }
1368
1369 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1370  * by the hash value TO_FIND and set *FOUND accordingly.
1371  *
1372  * Note: This function requires the caller to serialize access.
1373  * Don't call it directly, call entry_exists instead.
1374  */
1375 static svn_error_t *
1376 entry_exists_internal(svn_membuffer_t *cache,
1377                       apr_uint32_t group_index,
1378                       entry_key_t to_find,
1379                       svn_boolean_t *found)
1380 {
1381   *found = find_entry(cache, group_index, to_find, FALSE) != NULL;
1382   return SVN_NO_ERROR;
1383 }
1384
1385 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1386  * by the hash value TO_FIND and set *FOUND accordingly.
1387  */
1388 static svn_error_t *
1389 entry_exists(svn_membuffer_t *cache,
1390              apr_uint32_t group_index,
1391              entry_key_t to_find,
1392              svn_boolean_t *found)
1393 {
1394   WITH_READ_LOCK(cache,
1395                  entry_exists_internal(cache,
1396                                        group_index,
1397                                        to_find,
1398                                        found));
1399
1400   return SVN_NO_ERROR;
1401 }
1402
1403
1404 /* Try to insert the serialized item given in BUFFER with SIZE into
1405  * the group GROUP_INDEX of CACHE and uniquely identify it by hash
1406  * value TO_FIND.
1407  *
1408  * However, there is no guarantee that it will actually be put into
1409  * the cache. If there is already some data associated with TO_FIND,
1410  * it will be removed from the cache even if the new data cannot
1411  * be inserted.
1412  *
1413  * Note: This function requires the caller to serialization access.
1414  * Don't call it directly, call membuffer_cache_get_partial instead.
1415  */
1416 static svn_error_t *
1417 membuffer_cache_set_internal(svn_membuffer_t *cache,
1418                              entry_key_t to_find,
1419                              apr_uint32_t group_index,
1420                              char *buffer,
1421                              apr_size_t size,
1422                              DEBUG_CACHE_MEMBUFFER_TAG_ARG
1423                              apr_pool_t *scratch_pool)
1424 {
1425   /* first, look for a previous entry for the given key */
1426   entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
1427
1428   /* if there is an old version of that entry and the new data fits into
1429    * the old spot, just re-use that space. */
1430   if (entry && ALIGN_VALUE(entry->size) >= size && buffer)
1431     {
1432       /* Careful! We need to cast SIZE to the full width of CACHE->DATA_USED
1433        * lest we run into trouble with 32 bit underflow *not* treated as a
1434        * negative value.
1435        */
1436       cache->data_used += (apr_uint64_t)size - entry->size;
1437       entry->size = size;
1438
1439 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1440
1441       /* Remember original content, type and key (hashes)
1442        */
1443       SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
1444       memcpy(&entry->tag, tag, sizeof(*tag));
1445
1446 #endif
1447
1448       if (size)
1449         memcpy(cache->data + entry->offset, buffer, size);
1450
1451       cache->total_writes++;
1452       return SVN_NO_ERROR;
1453     }
1454
1455   /* if necessary, enlarge the insertion window.
1456    */
1457   if (   buffer != NULL
1458       && cache->max_entry_size >= size
1459       && ensure_data_insertable(cache, size))
1460     {
1461       /* Remove old data for this key, if that exists.
1462        * Get an unused entry for the key and and initialize it with
1463        * the serialized item's (future) position within data buffer.
1464        */
1465       entry = find_entry(cache, group_index, to_find, TRUE);
1466       entry->size = size;
1467       entry->offset = cache->current_data;
1468
1469 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1470
1471       /* Remember original content, type and key (hashes)
1472        */
1473       SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
1474       memcpy(&entry->tag, tag, sizeof(*tag));
1475
1476 #endif
1477
1478       /* Link the entry properly.
1479        */
1480       insert_entry(cache, entry);
1481
1482       /* Copy the serialized item data into the cache.
1483        */
1484       if (size)
1485         memcpy(cache->data + entry->offset, buffer, size);
1486
1487       cache->total_writes++;
1488     }
1489   else
1490     {
1491       /* if there is already an entry for this key, drop it.
1492        * Since ensure_data_insertable may have removed entries from
1493        * ENTRY's group, re-do the lookup.
1494        */
1495       entry = find_entry(cache, group_index, to_find, FALSE);
1496       if (entry)
1497         drop_entry(cache, entry);
1498     }
1499
1500   return SVN_NO_ERROR;
1501 }
1502
1503 /* Try to insert the ITEM and use the KEY to uniquely identify it.
1504  * However, there is no guarantee that it will actually be put into
1505  * the cache. If there is already some data associated to the KEY,
1506  * it will be removed from the cache even if the new data cannot
1507  * be inserted.
1508  *
1509  * The SERIALIZER is called to transform the ITEM into a single,
1510  * flat data buffer. Temporary allocations may be done in POOL.
1511  */
1512 static svn_error_t *
1513 membuffer_cache_set(svn_membuffer_t *cache,
1514                     entry_key_t key,
1515                     void *item,
1516                     svn_cache__serialize_func_t serializer,
1517                     DEBUG_CACHE_MEMBUFFER_TAG_ARG
1518                     apr_pool_t *scratch_pool)
1519 {
1520   apr_uint32_t group_index;
1521   void *buffer = NULL;
1522   apr_size_t size = 0;
1523
1524   /* find the entry group that will hold the key.
1525    */
1526   group_index = get_group_index(&cache, key);
1527
1528   /* Serialize data data.
1529    */
1530   if (item)
1531     SVN_ERR(serializer(&buffer, &size, item, scratch_pool));
1532
1533   /* The actual cache data access needs to sync'ed
1534    */
1535   WITH_WRITE_LOCK(cache,
1536                   membuffer_cache_set_internal(cache,
1537                                                key,
1538                                                group_index,
1539                                                buffer,
1540                                                size,
1541                                                DEBUG_CACHE_MEMBUFFER_TAG
1542                                                scratch_pool));
1543   return SVN_NO_ERROR;
1544 }
1545
1546 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1547  * by the hash value TO_FIND. If no item has been stored for KEY,
1548  * *BUFFER will be NULL. Otherwise, return a copy of the serialized
1549  * data in *BUFFER and return its size in *ITEM_SIZE. Allocations will
1550  * be done in POOL.
1551  *
1552  * Note: This function requires the caller to serialization access.
1553  * Don't call it directly, call membuffer_cache_get_partial instead.
1554  */
1555 static svn_error_t *
1556 membuffer_cache_get_internal(svn_membuffer_t *cache,
1557                              apr_uint32_t group_index,
1558                              entry_key_t to_find,
1559                              char **buffer,
1560                              apr_size_t *item_size,
1561                              DEBUG_CACHE_MEMBUFFER_TAG_ARG
1562                              apr_pool_t *result_pool)
1563 {
1564   entry_t *entry;
1565   apr_size_t size;
1566
1567   /* The actual cache data access needs to sync'ed
1568    */
1569   entry = find_entry(cache, group_index, to_find, FALSE);
1570   cache->total_reads++;
1571   if (entry == NULL)
1572     {
1573       /* no such entry found.
1574        */
1575       *buffer = NULL;
1576       *item_size = 0;
1577
1578       return SVN_NO_ERROR;
1579     }
1580
1581   size = ALIGN_VALUE(entry->size);
1582   *buffer = ALIGN_POINTER(apr_palloc(result_pool, size + ITEM_ALIGNMENT-1));
1583   memcpy(*buffer, (const char*)cache->data + entry->offset, size);
1584
1585 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1586
1587   /* Check for overlapping entries.
1588    */
1589   SVN_ERR_ASSERT(entry->next == NO_INDEX ||
1590                  entry->offset + size
1591                     <= get_entry(cache, entry->next)->offset);
1592
1593   /* Compare original content, type and key (hashes)
1594    */
1595   SVN_ERR(store_content_part(tag, *buffer, entry->size, result_pool));
1596   SVN_ERR(assert_equal_tags(&entry->tag, tag));
1597
1598 #endif
1599
1600   /* update hit statistics
1601    */
1602   entry->hit_count++;
1603   cache->hit_count++;
1604   cache->total_hits++;
1605
1606   *item_size = entry->size;
1607
1608   return SVN_NO_ERROR;
1609 }
1610
1611 /* Look for the *ITEM identified by KEY. If no item has been stored
1612  * for KEY, *ITEM will be NULL. Otherwise, the DESERIALIZER is called
1613  * re-construct the proper object from the serialized data.
1614  * Allocations will be done in POOL.
1615  */
1616 static svn_error_t *
1617 membuffer_cache_get(svn_membuffer_t *cache,
1618                     entry_key_t key,
1619                     void **item,
1620                     svn_cache__deserialize_func_t deserializer,
1621                     DEBUG_CACHE_MEMBUFFER_TAG_ARG
1622                     apr_pool_t *result_pool)
1623 {
1624   apr_uint32_t group_index;
1625   char *buffer;
1626   apr_size_t size;
1627
1628   /* find the entry group that will hold the key.
1629    */
1630   group_index = get_group_index(&cache, key);
1631   WITH_READ_LOCK(cache,
1632                  membuffer_cache_get_internal(cache,
1633                                               group_index,
1634                                               key,
1635                                               &buffer,
1636                                               &size,
1637                                               DEBUG_CACHE_MEMBUFFER_TAG
1638                                               result_pool));
1639
1640   /* re-construct the original data object from its serialized form.
1641    */
1642   if (buffer == NULL)
1643     {
1644       *item = NULL;
1645       return SVN_NO_ERROR;
1646     }
1647
1648   return deserializer(item, buffer, size, result_pool);
1649 }
1650
1651 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1652  * by the hash value TO_FIND. FOUND indicates whether that entry exists.
1653  * If not found, *ITEM will be NULL.
1654  *
1655  * Otherwise, the DESERIALIZER is called with that entry and the BATON
1656  * provided and will extract the desired information. The result is set
1657  * in *ITEM. Allocations will be done in POOL.
1658  *
1659  * Note: This function requires the caller to serialization access.
1660  * Don't call it directly, call membuffer_cache_get_partial instead.
1661  */
1662 static svn_error_t *
1663 membuffer_cache_get_partial_internal(svn_membuffer_t *cache,
1664                                      apr_uint32_t group_index,
1665                                      entry_key_t to_find,
1666                                      void **item,
1667                                      svn_boolean_t *found,
1668                                      svn_cache__partial_getter_func_t deserializer,
1669                                      void *baton,
1670                                      DEBUG_CACHE_MEMBUFFER_TAG_ARG
1671                                      apr_pool_t *result_pool)
1672 {
1673   entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
1674   cache->total_reads++;
1675   if (entry == NULL)
1676     {
1677       *item = NULL;
1678       *found = FALSE;
1679
1680       return SVN_NO_ERROR;
1681     }
1682   else
1683     {
1684       *found = TRUE;
1685
1686       entry->hit_count++;
1687       cache->hit_count++;
1688       cache->total_hits++;
1689
1690 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1691
1692       /* Check for overlapping entries.
1693        */
1694       SVN_ERR_ASSERT(entry->next == NO_INDEX ||
1695                      entry->offset + entry->size
1696                         <= get_entry(cache, entry->next)->offset);
1697
1698       /* Compare original content, type and key (hashes)
1699        */
1700       SVN_ERR(store_content_part(tag,
1701                                  (const char*)cache->data + entry->offset,
1702                                  entry->size,
1703                                  result_pool));
1704       SVN_ERR(assert_equal_tags(&entry->tag, tag));
1705
1706 #endif
1707
1708       return deserializer(item,
1709                           (const char*)cache->data + entry->offset,
1710                           entry->size,
1711                           baton,
1712                           result_pool);
1713     }
1714 }
1715
1716 /* Look for the cache entry identified by KEY. FOUND indicates
1717  * whether that entry exists. If not found, *ITEM will be NULL. Otherwise,
1718  * the DESERIALIZER is called with that entry and the BATON provided
1719  * and will extract the desired information. The result is set in *ITEM.
1720  * Allocations will be done in POOL.
1721  */
1722 static svn_error_t *
1723 membuffer_cache_get_partial(svn_membuffer_t *cache,
1724                             entry_key_t key,
1725                             void **item,
1726                             svn_boolean_t *found,
1727                             svn_cache__partial_getter_func_t deserializer,
1728                             void *baton,
1729                             DEBUG_CACHE_MEMBUFFER_TAG_ARG
1730                             apr_pool_t *result_pool)
1731 {
1732   apr_uint32_t group_index = get_group_index(&cache, key);
1733
1734   WITH_READ_LOCK(cache,
1735                  membuffer_cache_get_partial_internal
1736                      (cache, group_index, key, item, found,
1737                       deserializer, baton, DEBUG_CACHE_MEMBUFFER_TAG
1738                       result_pool));
1739
1740   return SVN_NO_ERROR;
1741 }
1742
1743 /* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1744  * by the hash value TO_FIND. If no entry has been found, the function
1745  * returns without modifying the cache.
1746  *
1747  * Otherwise, FUNC is called with that entry and the BATON provided
1748  * and may modify the cache entry. Allocations will be done in POOL.
1749  *
1750  * Note: This function requires the caller to serialization access.
1751  * Don't call it directly, call membuffer_cache_set_partial instead.
1752  */
1753 static svn_error_t *
1754 membuffer_cache_set_partial_internal(svn_membuffer_t *cache,
1755                                      apr_uint32_t group_index,
1756                                      entry_key_t to_find,
1757                                      svn_cache__partial_setter_func_t func,
1758                                      void *baton,
1759                                      DEBUG_CACHE_MEMBUFFER_TAG_ARG
1760                                      apr_pool_t *scratch_pool)
1761 {
1762   /* cache item lookup
1763    */
1764   entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
1765   cache->total_reads++;
1766
1767   /* this function is a no-op if the item is not in cache
1768    */
1769   if (entry != NULL)
1770     {
1771       svn_error_t *err;
1772
1773       /* access the serialized cache item */
1774       char *data = (char*)cache->data + entry->offset;
1775       char *orig_data = data;
1776       apr_size_t size = entry->size;
1777
1778       entry->hit_count++;
1779       cache->hit_count++;
1780       cache->total_writes++;
1781
1782 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1783
1784       /* Check for overlapping entries.
1785        */
1786       SVN_ERR_ASSERT(entry->next == NO_INDEX ||
1787                      entry->offset + size
1788                         <= get_entry(cache, entry->next)->offset);
1789
1790       /* Compare original content, type and key (hashes)
1791        */
1792       SVN_ERR(store_content_part(tag, data, size, scratch_pool));
1793       SVN_ERR(assert_equal_tags(&entry->tag, tag));
1794
1795 #endif
1796
1797       /* modify it, preferably in-situ.
1798        */
1799       err = func((void **)&data, &size, baton, scratch_pool);
1800
1801       if (err)
1802         {
1803           /* Something somewhere when wrong while FUNC was modifying the
1804            * changed item. Thus, it might have become invalid /corrupted.
1805            * We better drop that.
1806            */
1807           drop_entry(cache, entry);
1808         }
1809       else
1810         {
1811           /* if the modification caused a re-allocation, we need to remove
1812            * the old entry and to copy the new data back into cache.
1813            */
1814           if (data != orig_data)
1815             {
1816               /* Remove the old entry and try to make space for the new one.
1817                */
1818               drop_entry(cache, entry);
1819               if (   (cache->max_entry_size >= size)
1820                   && ensure_data_insertable(cache, size))
1821                 {
1822                   /* Write the new entry.
1823                    */
1824                   entry = find_entry(cache, group_index, to_find, TRUE);
1825                   entry->size = size;
1826                   entry->offset = cache->current_data;
1827                   if (size)
1828                     memcpy(cache->data + entry->offset, data, size);
1829
1830                   /* Link the entry properly.
1831                    */
1832                   insert_entry(cache, entry);
1833                 }
1834             }
1835
1836 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1837
1838           /* Remember original content, type and key (hashes)
1839            */
1840           SVN_ERR(store_content_part(tag, data, size, scratch_pool));
1841           memcpy(&entry->tag, tag, sizeof(*tag));
1842
1843 #endif
1844         }
1845     }
1846
1847   return SVN_NO_ERROR;
1848 }
1849
1850 /* Look for the cache entry identified by KEY. If no entry
1851  * has been found, the function returns without modifying the cache.
1852  * Otherwise, FUNC is called with that entry and the BATON provided
1853  * and may modify the cache entry. Allocations will be done in POOL.
1854  */
1855 static svn_error_t *
1856 membuffer_cache_set_partial(svn_membuffer_t *cache,
1857                             entry_key_t key,
1858                             svn_cache__partial_setter_func_t func,
1859                             void *baton,
1860                             DEBUG_CACHE_MEMBUFFER_TAG_ARG
1861                             apr_pool_t *scratch_pool)
1862 {
1863   /* cache item lookup
1864    */
1865   apr_uint32_t group_index = get_group_index(&cache, key);
1866   WITH_WRITE_LOCK(cache,
1867                   membuffer_cache_set_partial_internal
1868                      (cache, group_index, key, func, baton,
1869                       DEBUG_CACHE_MEMBUFFER_TAG
1870                       scratch_pool));
1871
1872   /* done here -> unlock the cache
1873    */
1874   return SVN_NO_ERROR;
1875 }
1876
1877 /* Implement the svn_cache__t interface on top of a shared membuffer cache.
1878  *
1879  * Because membuffer caches tend to be very large, there will be rather few
1880  * of them (usually only one). Thus, the same instance shall be used as the
1881  * backend to many application-visible svn_cache__t instances. This should
1882  * also achieve global resource usage fairness.
1883  *
1884  * To accommodate items from multiple resources, the individual keys must be
1885  * unique over all sources. This is achieved by simply adding a prefix key
1886  * that unambiguously identifies the item's context (e.g. path to the
1887  * respective repository). The prefix will be set upon construction of the
1888  * svn_cache__t instance.
1889  */
1890
1891 /* Internal cache structure (used in svn_cache__t.cache_internal) basically
1892  * holding the additional parameters needed to call the respective membuffer
1893  * functions.
1894  */
1895 typedef struct svn_membuffer_cache_t
1896 {
1897   /* this is where all our data will end up in
1898    */
1899   svn_membuffer_t *membuffer;
1900
1901   /* use this conversion function when inserting an item into the memcache
1902    */
1903   svn_cache__serialize_func_t serializer;
1904
1905   /* use this conversion function when reading an item from the memcache
1906    */
1907   svn_cache__deserialize_func_t deserializer;
1908
1909   /* Prepend this byte sequence to any key passed to us.
1910    * This makes (very likely) our keys different from all keys used
1911    * by other svn_membuffer_cache_t instances.
1912    */
1913   entry_key_t prefix;
1914
1915   /* A copy of the unmodified prefix. It is being used as a user-visible
1916    * ID for this cache instance.
1917    */
1918   const char* full_prefix;
1919
1920   /* length of the keys that will be passed to us through the
1921    * svn_cache_t interface. May be APR_HASH_KEY_STRING.
1922    */
1923   apr_ssize_t key_len;
1924
1925   /* Temporary buffer containing the hash key for the current access
1926    */
1927   entry_key_t combined_key;
1928
1929   /* a pool for temporary allocations during get() and set()
1930    */
1931   apr_pool_t *pool;
1932
1933   /* an internal counter that is used to clear the pool from time to time
1934    * but not too frequently.
1935    */
1936   int alloc_counter;
1937
1938   /* if enabled, this will serialize the access to this instance.
1939    */
1940   svn_mutex__t *mutex;
1941 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
1942
1943   /* Invariant tag info for all items stored by this cache instance.
1944    */
1945   char prefix_tail[PREFIX_TAIL_LEN];
1946
1947 #endif
1948 } svn_membuffer_cache_t;
1949
1950 /* After an estimated ALLOCATIONS_PER_POOL_CLEAR allocations, we should
1951  * clear the svn_membuffer_cache_t.pool to keep memory consumption in check.
1952  */
1953 #define ALLOCATIONS_PER_POOL_CLEAR 10
1954
1955
1956 /* Basically calculate a hash value for KEY of length KEY_LEN, combine it
1957  * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY.
1958  */
1959 static void
1960 combine_key(svn_membuffer_cache_t *cache,
1961             const void *key,
1962             apr_ssize_t key_len)
1963 {
1964   if (key_len == APR_HASH_KEY_STRING)
1965     key_len = strlen((const char *) key);
1966
1967   if (key_len < 16)
1968     {
1969       apr_uint32_t data[4] = { 0 };
1970       memcpy(data, key, key_len);
1971
1972       svn__pseudo_md5_15((apr_uint32_t *)cache->combined_key, data);
1973     }
1974   else if (key_len < 32)
1975     {
1976       apr_uint32_t data[8] = { 0 };
1977       memcpy(data, key, key_len);
1978
1979       svn__pseudo_md5_31((apr_uint32_t *)cache->combined_key, data);
1980     }
1981   else if (key_len < 64)
1982     {
1983       apr_uint32_t data[16] = { 0 };
1984       memcpy(data, key, key_len);
1985
1986       svn__pseudo_md5_63((apr_uint32_t *)cache->combined_key, data);
1987     }
1988   else
1989     {
1990       apr_md5((unsigned char*)cache->combined_key, key, key_len);
1991     }
1992
1993   cache->combined_key[0] ^= cache->prefix[0];
1994   cache->combined_key[1] ^= cache->prefix[1];
1995 }
1996
1997 /* Implement svn_cache__vtable_t.get (not thread-safe)
1998  */
1999 static svn_error_t *
2000 svn_membuffer_cache_get(void **value_p,
2001                         svn_boolean_t *found,
2002                         void *cache_void,
2003                         const void *key,
2004                         apr_pool_t *result_pool)
2005 {
2006   svn_membuffer_cache_t *cache = cache_void;
2007
2008   DEBUG_CACHE_MEMBUFFER_INIT_TAG
2009
2010   /* special case */
2011   if (key == NULL)
2012     {
2013       *value_p = NULL;
2014       *found = FALSE;
2015
2016       return SVN_NO_ERROR;
2017     }
2018
2019   /* construct the full, i.e. globally unique, key by adding
2020    * this cache instances' prefix
2021    */
2022   combine_key(cache, key, cache->key_len);
2023
2024   /* Look the item up. */
2025   SVN_ERR(membuffer_cache_get(cache->membuffer,
2026                               cache->combined_key,
2027                               value_p,
2028                               cache->deserializer,
2029                               DEBUG_CACHE_MEMBUFFER_TAG
2030                               result_pool));
2031
2032   /* return result */
2033   *found = *value_p != NULL;
2034   return SVN_NO_ERROR;
2035 }
2036
2037 /* Implement svn_cache__vtable_t.set (not thread-safe)
2038  */
2039 static svn_error_t *
2040 svn_membuffer_cache_set(void *cache_void,
2041                         const void *key,
2042                         void *value,
2043                         apr_pool_t *scratch_pool)
2044 {
2045   svn_membuffer_cache_t *cache = cache_void;
2046
2047   DEBUG_CACHE_MEMBUFFER_INIT_TAG
2048
2049   /* special case */
2050   if (key == NULL)
2051     return SVN_NO_ERROR;
2052
2053   /* we do some allocations below, so increase the allocation counter
2054    * by a slightly larger amount. Free allocated memory every now and then.
2055    */
2056   cache->alloc_counter += 3;
2057   if (cache->alloc_counter > ALLOCATIONS_PER_POOL_CLEAR)
2058     {
2059       svn_pool_clear(cache->pool);
2060       cache->alloc_counter = 0;
2061     }
2062
2063   /* construct the full, i.e. globally unique, key by adding
2064    * this cache instances' prefix
2065    */
2066   combine_key(cache, key, cache->key_len);
2067
2068   /* (probably) add the item to the cache. But there is no real guarantee
2069    * that the item will actually be cached afterwards.
2070    */
2071   return membuffer_cache_set(cache->membuffer,
2072                              cache->combined_key,
2073                              value,
2074                              cache->serializer,
2075                              DEBUG_CACHE_MEMBUFFER_TAG
2076                              cache->pool);
2077 }
2078
2079 /* Implement svn_cache__vtable_t.iter as "not implemented"
2080  */
2081 static svn_error_t *
2082 svn_membuffer_cache_iter(svn_boolean_t *completed,
2083                           void *cache_void,
2084                           svn_iter_apr_hash_cb_t user_cb,
2085                           void *user_baton,
2086                           apr_pool_t *scratch_pool)
2087 {
2088   return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2089                           _("Can't iterate a membuffer-based cache"));
2090 }
2091
2092 /* Implement svn_cache__vtable_t.get_partial (not thread-safe)
2093  */
2094 static svn_error_t *
2095 svn_membuffer_cache_get_partial(void **value_p,
2096                                 svn_boolean_t *found,
2097                                 void *cache_void,
2098                                 const void *key,
2099                                 svn_cache__partial_getter_func_t func,
2100                                 void *baton,
2101                                 apr_pool_t *result_pool)
2102 {
2103   svn_membuffer_cache_t *cache = cache_void;
2104
2105   DEBUG_CACHE_MEMBUFFER_INIT_TAG
2106
2107   if (key == NULL)
2108     {
2109       *value_p = NULL;
2110       *found = FALSE;
2111
2112       return SVN_NO_ERROR;
2113     }
2114
2115   combine_key(cache, key, cache->key_len);
2116   SVN_ERR(membuffer_cache_get_partial(cache->membuffer,
2117                                       cache->combined_key,
2118                                       value_p,
2119                                       found,
2120                                       func,
2121                                       baton,
2122                                       DEBUG_CACHE_MEMBUFFER_TAG
2123                                       result_pool));
2124
2125   return SVN_NO_ERROR;
2126 }
2127
2128 /* Implement svn_cache__vtable_t.set_partial (not thread-safe)
2129  */
2130 static svn_error_t *
2131 svn_membuffer_cache_set_partial(void *cache_void,
2132                                 const void *key,
2133                                 svn_cache__partial_setter_func_t func,
2134                                 void *baton,
2135                                 apr_pool_t *scratch_pool)
2136 {
2137   svn_membuffer_cache_t *cache = cache_void;
2138
2139   DEBUG_CACHE_MEMBUFFER_INIT_TAG
2140
2141   if (key != NULL)
2142     {
2143       combine_key(cache, key, cache->key_len);
2144       SVN_ERR(membuffer_cache_set_partial(cache->membuffer,
2145                                           cache->combined_key,
2146                                           func,
2147                                           baton,
2148                                           DEBUG_CACHE_MEMBUFFER_TAG
2149                                           scratch_pool));
2150     }
2151   return SVN_NO_ERROR;
2152 }
2153
2154 /* Implement svn_cache__vtable_t.is_cachable
2155  * (thread-safe even without mutex)
2156  */
2157 static svn_boolean_t
2158 svn_membuffer_cache_is_cachable(void *cache_void, apr_size_t size)
2159 {
2160   /* Don't allow extremely large element sizes. Otherwise, the cache
2161    * might by thrashed by a few extremely large entries. And the size
2162    * must be small enough to be stored in a 32 bit value.
2163    */
2164   svn_membuffer_cache_t *cache = cache_void;
2165   return size <= cache->membuffer->max_entry_size;
2166 }
2167
2168 /* Add statistics of SEGMENT to INFO.
2169  */
2170 static svn_error_t *
2171 svn_membuffer_get_segment_info(svn_membuffer_t *segment,
2172                                svn_cache__info_t *info)
2173 {
2174   info->data_size += segment->data_size;
2175   info->used_size += segment->data_used;
2176   info->total_size += segment->data_size +
2177       segment->group_count * GROUP_SIZE * sizeof(entry_t);
2178
2179   info->used_entries += segment->used_entries;
2180   info->total_entries += segment->group_count * GROUP_SIZE;
2181
2182   return SVN_NO_ERROR;
2183 }
2184
2185 /* Implement svn_cache__vtable_t.get_info
2186  * (thread-safe even without mutex)
2187  */
2188 static svn_error_t *
2189 svn_membuffer_cache_get_info(void *cache_void,
2190                              svn_cache__info_t *info,
2191                              svn_boolean_t reset,
2192                              apr_pool_t *result_pool)
2193 {
2194   svn_membuffer_cache_t *cache = cache_void;
2195   apr_uint32_t i;
2196
2197   /* cache front-end specific data */
2198
2199   info->id = apr_pstrdup(result_pool, cache->full_prefix);
2200
2201   /* collect info from shared cache back-end */
2202
2203   info->data_size = 0;
2204   info->used_size = 0;
2205   info->total_size = 0;
2206
2207   info->used_entries = 0;
2208   info->total_entries = 0;
2209
2210   for (i = 0; i < cache->membuffer->segment_count; ++i)
2211     {
2212       svn_membuffer_t *segment = cache->membuffer + i;
2213       WITH_READ_LOCK(segment,
2214                      svn_membuffer_get_segment_info(segment, info));
2215     }
2216
2217   return SVN_NO_ERROR;
2218 }
2219
2220
2221 /* the v-table for membuffer-based caches (single-threaded access)
2222  */
2223 static svn_cache__vtable_t membuffer_cache_vtable = {
2224   svn_membuffer_cache_get,
2225   svn_membuffer_cache_set,
2226   svn_membuffer_cache_iter,
2227   svn_membuffer_cache_is_cachable,
2228   svn_membuffer_cache_get_partial,
2229   svn_membuffer_cache_set_partial,
2230   svn_membuffer_cache_get_info
2231 };
2232
2233 /* Implement svn_cache__vtable_t.get and serialize all cache access.
2234  */
2235 static svn_error_t *
2236 svn_membuffer_cache_get_synced(void **value_p,
2237                                svn_boolean_t *found,
2238                                void *cache_void,
2239                                const void *key,
2240                                apr_pool_t *result_pool)
2241 {
2242   svn_membuffer_cache_t *cache = cache_void;
2243   SVN_MUTEX__WITH_LOCK(cache->mutex,
2244                        svn_membuffer_cache_get(value_p,
2245                                                found,
2246                                                cache_void,
2247                                                key,
2248                                                result_pool));
2249
2250   return SVN_NO_ERROR;
2251 }
2252
2253 /* Implement svn_cache__vtable_t.set and serialize all cache access.
2254  */
2255 static svn_error_t *
2256 svn_membuffer_cache_set_synced(void *cache_void,
2257                                const void *key,
2258                                void *value,
2259                                apr_pool_t *scratch_pool)
2260 {
2261   svn_membuffer_cache_t *cache = cache_void;
2262   SVN_MUTEX__WITH_LOCK(cache->mutex,
2263                        svn_membuffer_cache_set(cache_void,
2264                                                key,
2265                                                value,
2266                                                scratch_pool));
2267
2268   return SVN_NO_ERROR;
2269 }
2270
2271 /* Implement svn_cache__vtable_t.get_partial and serialize all cache access.
2272  */
2273 static svn_error_t *
2274 svn_membuffer_cache_get_partial_synced(void **value_p,
2275                                        svn_boolean_t *found,
2276                                        void *cache_void,
2277                                        const void *key,
2278                                        svn_cache__partial_getter_func_t func,
2279                                        void *baton,
2280                                        apr_pool_t *result_pool)
2281 {
2282   svn_membuffer_cache_t *cache = cache_void;
2283   SVN_MUTEX__WITH_LOCK(cache->mutex,
2284                        svn_membuffer_cache_get_partial(value_p,
2285                                                        found,
2286                                                        cache_void,
2287                                                        key,
2288                                                        func,
2289                                                        baton,
2290                                                        result_pool));
2291
2292   return SVN_NO_ERROR;
2293 }
2294
2295 /* Implement svn_cache__vtable_t.set_partial and serialize all cache access.
2296  */
2297 static svn_error_t *
2298 svn_membuffer_cache_set_partial_synced(void *cache_void,
2299                                        const void *key,
2300                                        svn_cache__partial_setter_func_t func,
2301                                        void *baton,
2302                                        apr_pool_t *scratch_pool)
2303 {
2304   svn_membuffer_cache_t *cache = cache_void;
2305   SVN_MUTEX__WITH_LOCK(cache->mutex,
2306                        svn_membuffer_cache_set_partial(cache_void,
2307                                                        key,
2308                                                        func,
2309                                                        baton,
2310                                                        scratch_pool));
2311
2312   return SVN_NO_ERROR;
2313 }
2314
2315 /* the v-table for membuffer-based caches with multi-threading support)
2316  */
2317 static svn_cache__vtable_t membuffer_cache_synced_vtable = {
2318   svn_membuffer_cache_get_synced,
2319   svn_membuffer_cache_set_synced,
2320   svn_membuffer_cache_iter,               /* no sync required */
2321   svn_membuffer_cache_is_cachable,        /* no sync required */
2322   svn_membuffer_cache_get_partial_synced,
2323   svn_membuffer_cache_set_partial_synced,
2324   svn_membuffer_cache_get_info            /* no sync required */
2325 };
2326
2327 /* standard serialization function for svn_stringbuf_t items.
2328  * Implements svn_cache__serialize_func_t.
2329  */
2330 static svn_error_t *
2331 serialize_svn_stringbuf(void **buffer,
2332                         apr_size_t *buffer_size,
2333                         void *item,
2334                         apr_pool_t *result_pool)
2335 {
2336   svn_stringbuf_t *value_str = item;
2337
2338   *buffer = value_str->data;
2339   *buffer_size = value_str->len + 1;
2340
2341   return SVN_NO_ERROR;
2342 }
2343
2344 /* standard de-serialization function for svn_stringbuf_t items.
2345  * Implements svn_cache__deserialize_func_t.
2346  */
2347 static svn_error_t *
2348 deserialize_svn_stringbuf(void **item,
2349                           void *buffer,
2350                           apr_size_t buffer_size,
2351                           apr_pool_t *result_pool)
2352 {
2353   svn_stringbuf_t *value_str = apr_palloc(result_pool, sizeof(svn_stringbuf_t));
2354
2355   value_str->pool = result_pool;
2356   value_str->blocksize = buffer_size;
2357   value_str->data = buffer;
2358   value_str->len = buffer_size-1;
2359   *item = value_str;
2360
2361   return SVN_NO_ERROR;
2362 }
2363
2364 /* Construct a svn_cache__t object on top of a shared memcache.
2365  */
2366 svn_error_t *
2367 svn_cache__create_membuffer_cache(svn_cache__t **cache_p,
2368                                   svn_membuffer_t *membuffer,
2369                                   svn_cache__serialize_func_t serializer,
2370                                   svn_cache__deserialize_func_t deserializer,
2371                                   apr_ssize_t klen,
2372                                   const char *prefix,
2373                                   svn_boolean_t thread_safe,
2374                                   apr_pool_t *pool)
2375 {
2376   svn_checksum_t *checksum;
2377
2378   /* allocate the cache header structures
2379    */
2380   svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
2381   svn_membuffer_cache_t *cache = apr_palloc(pool, sizeof(*cache));
2382
2383   /* initialize our internal cache header
2384    */
2385   cache->membuffer = membuffer;
2386   cache->serializer = serializer
2387                     ? serializer
2388                     : serialize_svn_stringbuf;
2389   cache->deserializer = deserializer
2390                       ? deserializer
2391                       : deserialize_svn_stringbuf;
2392   cache->full_prefix = apr_pstrdup(pool, prefix);
2393   cache->key_len = klen;
2394   cache->pool = svn_pool_create(pool);
2395   cache->alloc_counter = 0;
2396
2397   SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
2398
2399   /* for performance reasons, we don't actually store the full prefix but a
2400    * hash value of it
2401    */
2402   SVN_ERR(svn_checksum(&checksum,
2403                        svn_checksum_md5,
2404                        prefix,
2405                        strlen(prefix),
2406                        pool));
2407   memcpy(cache->prefix, checksum->digest, sizeof(cache->prefix));
2408
2409 #ifdef SVN_DEBUG_CACHE_MEMBUFFER
2410
2411   /* Initialize cache debugging support.
2412    */
2413   get_prefix_tail(prefix, cache->prefix_tail);
2414
2415 #endif
2416
2417   /* initialize the generic cache wrapper
2418    */
2419   wrapper->vtable = thread_safe ? &membuffer_cache_synced_vtable
2420                                 : &membuffer_cache_vtable;
2421   wrapper->cache_internal = cache;
2422   wrapper->error_handler = 0;
2423   wrapper->error_baton = 0;
2424
2425   *cache_p = wrapper;
2426   return SVN_NO_ERROR;
2427 }
2428