1251881Speter/*
2251881Speter * cache-membuffer.c: in-memory caching for Subversion
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include <assert.h>
25251881Speter#include <apr_md5.h>
26251881Speter#include <apr_thread_rwlock.h>
27251881Speter
28251881Speter#include "svn_pools.h"
29251881Speter#include "svn_checksum.h"
30251881Speter#include "md5.h"
31251881Speter#include "svn_private_config.h"
32251881Speter#include "cache.h"
33251881Speter#include "svn_string.h"
34251881Speter#include "private/svn_dep_compat.h"
35251881Speter#include "private/svn_mutex.h"
36251881Speter#include "private/svn_pseudo_md5.h"
37251881Speter
38251881Speter/*
39251881Speter * This svn_cache__t implementation actually consists of two parts:
40251881Speter * a shared (per-process) singleton membuffer cache instance and shallow
41251881Speter * svn_cache__t front-end instances that each use different key spaces.
42251881Speter * For data management, they all forward to the singleton membuffer cache.
43251881Speter *
44251881Speter * A membuffer cache consists of two parts:
45251881Speter *
46251881Speter * 1. A linear data buffer containing cached items in a serialized
47251881Speter *    representation. There may be arbitrary gaps between entries.
48251881Speter * 2. A directory of cache entries. This is organized similar to CPU
49251881Speter *    data caches: for every possible key, there is exactly one group
50251881Speter *    of entries that may contain the header info for an item with
51251881Speter *    that given key. The result is a GROUP_SIZE-way associative cache.
52251881Speter *
53251881Speter * Only the start address of these two data parts are given as a native
54251881Speter * pointer. All other references are expressed as offsets to these pointers.
55251881Speter * With that design, it is relatively easy to share the same data structure
56251881Speter * between different processes and / or to persist them on disk. These
57251881Speter * out-of-process features have not been implemented, yet.
58251881Speter *
59251881Speter * The data buffer usage information is implicitly given by the directory
60251881Speter * entries. Every USED entry has a reference to the previous and the next
61251881Speter * used dictionary entry and this double-linked list is ordered by the
62251881Speter * offsets of their item data within the data buffer. So removing data,
63251881Speter * for instance, is done simply by unlinking it from the chain, implicitly
64251881Speter * marking the entry as well as the data buffer section previously
65251881Speter * associated to it as unused.
66251881Speter *
67251881Speter * Insertion can occur at only one, sliding position. It is marked by its
68251881Speter * offset in the data buffer plus the index of the first used entry at or
69251881Speter * behind that position. If this gap is too small to accommodate the new
70251881Speter * item, the insertion window is extended as described below. The new entry
71251881Speter * will always be inserted at the bottom end of the window and since the
72251881Speter * next used entry is known, properly sorted insertion is possible.
73251881Speter *
74251881Speter * To make the cache perform robustly in a wide range of usage scenarios,
75251881Speter * a randomized variant of LFU is used (see ensure_data_insertable for
76251881Speter * details). Every item holds a read hit counter and there is a global read
77251881Speter * hit counter. The more hits an entry has in relation to the average, the
78251881Speter * more it is likely to be kept using a rand()-based condition. The test is
79251881Speter * applied only to the entry following the insertion window. If it doesn't
80251881Speter * get evicted, it is moved to the begin of that window and the window is
81251881Speter * moved.
82251881Speter *
83251881Speter * Moreover, the entry's hits get halved to make that entry more likely to
84251881Speter * be removed the next time the sliding insertion / removal window comes by.
85251881Speter * As a result, frequently used entries are likely not to be dropped until
86251881Speter * they get not used for a while. Also, even a cache thrashing situation
87251881Speter * about 50% of the content survives every 50% of the cache being re-written
88251881Speter * with new entries. For details on the fine-tuning involved, see the
89251881Speter * comments in ensure_data_insertable().
90251881Speter *
91251881Speter * To limit the entry size and management overhead, not the actual item keys
92251881Speter * but only their MD5 checksums will not be stored. This is reasonably safe
93251881Speter * to do since users have only limited control over the full keys, even if
94251881Speter * these contain folder paths. So, it is very hard to deliberately construct
95251881Speter * colliding keys. Random checksum collisions can be shown to be extremely
96251881Speter * unlikely.
97251881Speter *
98251881Speter * All access to the cached data needs to be serialized. Because we want
99251881Speter * to scale well despite that bottleneck, we simply segment the cache into
100251881Speter * a number of independent caches (segments). Items will be multiplexed based
101251881Speter * on their hash key.
102251881Speter */
103251881Speter
104251881Speter/* A 16-way associative cache seems to be a good compromise between
105251881Speter * performance (worst-case lookups) and efficiency-loss due to collisions.
106251881Speter *
107251881Speter * This value may be changed to any positive integer.
108251881Speter */
109251881Speter#define GROUP_SIZE 16
110251881Speter
111251881Speter/* For more efficient copy operations, let's align all data items properly.
112251881Speter * Must be a power of 2.
113251881Speter */
114251881Speter#define ITEM_ALIGNMENT 16
115251881Speter
116251881Speter/* By default, don't create cache segments smaller than this value unless
117251881Speter * the total cache size itself is smaller.
118251881Speter */
119251881Speter#define DEFAULT_MIN_SEGMENT_SIZE APR_UINT64_C(0x2000000)
120251881Speter
121251881Speter/* The minimum segment size we will allow for multi-segmented caches
122251881Speter */
123251881Speter#define MIN_SEGMENT_SIZE APR_UINT64_C(0x10000)
124251881Speter
125251881Speter/* The maximum number of segments allowed. Larger numbers reduce the size
126251881Speter * of each segment, in turn reducing the max size of a cachable item.
127251881Speter * Also, each segment gets its own lock object. The actual number supported
128251881Speter * by the OS may therefore be lower and svn_cache__membuffer_cache_create
129251881Speter * may return an error.
130251881Speter */
131251881Speter#define MAX_SEGMENT_COUNT 0x10000
132251881Speter
133251881Speter/* As of today, APR won't allocate chunks of 4GB or more. So, limit the
134251881Speter * segment size to slightly below that.
135251881Speter */
136251881Speter#define MAX_SEGMENT_SIZE APR_UINT64_C(0xffff0000)
137251881Speter
138251881Speter/* We don't mark the initialization status for every group but initialize
139251881Speter * a number of groups at once. That will allow for a very small init flags
140251881Speter * vector that is likely to fit into the CPU caches even for fairly large
141251881Speter * membuffer caches. For instance, the default of 32 means 8x32 groups per
142251881Speter * byte, i.e. 8 flags/byte x 32 groups/flag x 8 entries/group x 40 index
143251881Speter * bytes/entry x 8 cache bytes/index byte = 1kB init vector / 640MB cache.
144251881Speter */
145251881Speter#define GROUP_INIT_GRANULARITY 32
146251881Speter
147251881Speter/* Invalid index reference value. Equivalent to APR_UINT32_T(-1)
148251881Speter */
149251881Speter#define NO_INDEX APR_UINT32_MAX
150251881Speter
151251881Speter/* To save space in our group structure, we only use 32 bit size values
152251881Speter * and, therefore, limit the size of each entry to just below 4GB.
153251881Speter * Supporting larger items is not a good idea as the data transfer
154251881Speter * to and from the cache would block other threads for a very long time.
155251881Speter */
156251881Speter#define MAX_ITEM_SIZE ((apr_uint32_t)(0 - ITEM_ALIGNMENT))
157251881Speter
158251881Speter/* A 16 byte key type. We use that to identify cache entries.
159251881Speter * The notation as just two integer values will cause many compilers
160251881Speter * to create better code.
161251881Speter */
162251881Spetertypedef apr_uint64_t entry_key_t[2];
163251881Speter
164251881Speter/* Debugging / corruption detection support.
165251881Speter * If you define this macro, the getter functions will performed expensive
166251881Speter * checks on the item data, requested keys and entry types. If there is
167251881Speter * a mismatch found in any of them when being compared with the values
168251881Speter * remembered in the setter function, an error will be returned.
169251881Speter */
170251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
171251881Speter
172251881Speter/* The prefix passed to svn_cache__create_membuffer_cache() effectively
173251881Speter * defines the type of all items stored by that cache instance. We'll take
174251881Speter * the last 7 bytes + \0 as plaintext for easy identification by the dev.
175251881Speter */
176251881Speter#define PREFIX_TAIL_LEN 8
177251881Speter
178251881Speter/* This record will be attached to any cache entry. It tracks item data
179251881Speter * (content), key and type as hash values and is the baseline against which
180251881Speter * the getters will compare their results to detect inconsistencies.
181251881Speter */
182251881Spetertypedef struct entry_tag_t
183251881Speter{
184251881Speter  /* MD5 checksum over the serialized the item data.
185251881Speter   */
186251881Speter  unsigned char content_hash [APR_MD5_DIGESTSIZE];
187251881Speter
188251881Speter  /* Hash value of the svn_cache_t instance that wrote the item
189251881Speter   * (i.e. a combination of type and repository)
190251881Speter   */
191251881Speter  unsigned char prefix_hash [APR_MD5_DIGESTSIZE];
192251881Speter
193251881Speter  /* Note that this only covers the variable part of the key,
194251881Speter   * i.e. it will be different from the full key hash used for
195251881Speter   * cache indexing.
196251881Speter   */
197251881Speter  unsigned char key_hash [APR_MD5_DIGESTSIZE];
198251881Speter
199251881Speter  /* Last letters from of the key in human readable format
200251881Speter   * (ends with the type identifier, e.g. "DAG")
201251881Speter   */
202251881Speter  char prefix_tail[PREFIX_TAIL_LEN];
203251881Speter
204251881Speter  /* Length of the variable key part.
205251881Speter   */
206251881Speter  apr_size_t key_len;
207251881Speter
208251881Speter} entry_tag_t;
209251881Speter
210251881Speter/* Per svn_cache_t instance initialization helper.
211251881Speter */
212251881Speterstatic void get_prefix_tail(const char *prefix, char *prefix_tail)
213251881Speter{
214251881Speter  apr_size_t len = strlen(prefix);
215251881Speter  apr_size_t to_copy = len > PREFIX_TAIL_LEN-1 ? PREFIX_TAIL_LEN-1 : len;
216251881Speter
217251881Speter  memset(prefix_tail, 0, PREFIX_TAIL_LEN);
218251881Speter  memcpy(prefix_tail, prefix + len - to_copy, to_copy);
219251881Speter}
220251881Speter
221251881Speter/* Initialize all members of TAG except for the content hash.
222251881Speter */
223251881Speterstatic svn_error_t *store_key_part(entry_tag_t *tag,
224251881Speter                                   entry_key_t prefix_hash,
225251881Speter                                   char *prefix_tail,
226251881Speter                                   const void *key,
227251881Speter                                   apr_size_t key_len,
228251881Speter                                   apr_pool_t *pool)
229251881Speter{
230251881Speter  svn_checksum_t *checksum;
231251881Speter  SVN_ERR(svn_checksum(&checksum,
232251881Speter                       svn_checksum_md5,
233251881Speter                       key,
234251881Speter                       key_len,
235251881Speter                       pool));
236251881Speter
237251881Speter  memcpy(tag->prefix_hash, prefix_hash, sizeof(tag->prefix_hash));
238251881Speter  memcpy(tag->key_hash, checksum->digest, sizeof(tag->key_hash));
239251881Speter  memcpy(tag->prefix_tail, prefix_tail, sizeof(tag->prefix_tail));
240251881Speter
241251881Speter  tag->key_len = key_len;
242251881Speter
243251881Speter  return SVN_NO_ERROR;
244251881Speter}
245251881Speter
246251881Speter/* Initialize the content hash member of TAG.
247251881Speter */
248251881Speterstatic svn_error_t* store_content_part(entry_tag_t *tag,
249251881Speter                                       const char *data,
250251881Speter                                       apr_size_t size,
251251881Speter                                       apr_pool_t *pool)
252251881Speter{
253251881Speter  svn_checksum_t *checksum;
254251881Speter  SVN_ERR(svn_checksum(&checksum,
255251881Speter                       svn_checksum_md5,
256251881Speter                       data,
257251881Speter                       size,
258251881Speter                       pool));
259251881Speter
260251881Speter  memcpy(tag->content_hash, checksum->digest, sizeof(tag->content_hash));
261251881Speter
262251881Speter  return SVN_NO_ERROR;
263251881Speter}
264251881Speter
265251881Speter/* Compare two tags and fail with an assertion upon differences.
266251881Speter */
267251881Speterstatic svn_error_t* assert_equal_tags(const entry_tag_t *lhs,
268251881Speter                                      const entry_tag_t *rhs)
269251881Speter{
270251881Speter  SVN_ERR_ASSERT(memcmp(lhs->content_hash, rhs->content_hash,
271251881Speter                        sizeof(lhs->content_hash)) == 0);
272251881Speter  SVN_ERR_ASSERT(memcmp(lhs->prefix_hash, rhs->prefix_hash,
273251881Speter                        sizeof(lhs->prefix_hash)) == 0);
274251881Speter  SVN_ERR_ASSERT(memcmp(lhs->key_hash, rhs->key_hash,
275251881Speter                        sizeof(lhs->key_hash)) == 0);
276251881Speter  SVN_ERR_ASSERT(memcmp(lhs->prefix_tail, rhs->prefix_tail,
277251881Speter                        sizeof(lhs->prefix_tail)) == 0);
278251881Speter
279251881Speter  SVN_ERR_ASSERT(lhs->key_len == rhs->key_len);
280251881Speter
281251881Speter  return SVN_NO_ERROR;
282251881Speter}
283251881Speter
284251881Speter/* Reoccurring code snippets.
285251881Speter */
286251881Speter
287251881Speter#define DEBUG_CACHE_MEMBUFFER_TAG_ARG entry_tag_t *tag,
288251881Speter
289251881Speter#define DEBUG_CACHE_MEMBUFFER_TAG tag,
290251881Speter
291251881Speter#define DEBUG_CACHE_MEMBUFFER_INIT_TAG                         \
292251881Speter  entry_tag_t _tag;                                            \
293251881Speter  entry_tag_t *tag = &_tag;                                    \
294251881Speter  SVN_ERR(store_key_part(tag,                                  \
295251881Speter                         cache->prefix,                        \
296251881Speter                         cache->prefix_tail,                   \
297251881Speter                         key,                                  \
298251881Speter                         cache->key_len == APR_HASH_KEY_STRING \
299251881Speter                             ? strlen((const char *) key)      \
300251881Speter                             : cache->key_len,                 \
301251881Speter                         cache->pool));
302251881Speter
303251881Speter#else
304251881Speter
305251881Speter/* Don't generate any checks if consistency checks have not been enabled.
306251881Speter */
307251881Speter#define DEBUG_CACHE_MEMBUFFER_TAG_ARG
308251881Speter#define DEBUG_CACHE_MEMBUFFER_TAG
309251881Speter#define DEBUG_CACHE_MEMBUFFER_INIT_TAG
310251881Speter
311251881Speter#endif /* SVN_DEBUG_CACHE_MEMBUFFER */
312251881Speter
313251881Speter/* A single dictionary entry. Since all entries will be allocated once
314251881Speter * during cache creation, those entries might be either used or unused.
315251881Speter * An entry is used if and only if it is contained in the doubly-linked
316251881Speter * list of used entries.
317251881Speter */
318251881Spetertypedef struct entry_t
319251881Speter{
320251881Speter  /* Identifying the data item. Only valid for used entries.
321251881Speter   */
322251881Speter  entry_key_t key;
323251881Speter
324251881Speter  /* The offset of the cached item's serialized data within the data buffer.
325251881Speter   */
326251881Speter  apr_uint64_t offset;
327251881Speter
328251881Speter  /* Size of the serialized item data. May be 0.
329251881Speter   * Only valid for used entries.
330251881Speter   */
331251881Speter  apr_size_t size;
332251881Speter
333251881Speter  /* Number of (read) hits for this entry. Will be reset upon write.
334251881Speter   * Only valid for used entries.
335251881Speter   */
336251881Speter  apr_uint32_t hit_count;
337251881Speter
338251881Speter  /* Reference to the next used entry in the order defined by offset.
339251881Speter   * NO_INDEX indicates the end of the list; this entry must be referenced
340251881Speter   * by the caches membuffer_cache_t.last member. NO_INDEX also implies
341251881Speter   * that the data buffer is not used beyond offset+size.
342251881Speter   * Only valid for used entries.
343251881Speter   */
344251881Speter  apr_uint32_t next;
345251881Speter
346251881Speter  /* Reference to the previous used entry in the order defined by offset.
347251881Speter   * NO_INDEX indicates the end of the list; this entry must be referenced
348251881Speter   * by the caches membuffer_cache_t.first member.
349251881Speter   * Only valid for used entries.
350251881Speter   */
351251881Speter  apr_uint32_t previous;
352251881Speter
353251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
354251881Speter  /* Remember type, content and key hashes.
355251881Speter   */
356251881Speter  entry_tag_t tag;
357251881Speter#endif
358251881Speter} entry_t;
359251881Speter
360251881Speter/* We group dictionary entries to make this GROUP-SIZE-way associative.
361251881Speter */
362251881Spetertypedef struct entry_group_t
363251881Speter{
364251881Speter  /* number of entries used [0 .. USED-1] */
365251881Speter  apr_uint32_t used;
366251881Speter
367251881Speter  /* the actual entries */
368251881Speter  entry_t entries[GROUP_SIZE];
369251881Speter} entry_group_t;
370251881Speter
371251881Speter/* The cache header structure.
372251881Speter */
373251881Speterstruct svn_membuffer_t
374251881Speter{
375251881Speter  /* Number of cache segments. Must be a power of 2.
376251881Speter     Please note that this structure represents only one such segment
377251881Speter     and that all segments must / will report the same values here. */
378251881Speter  apr_uint32_t segment_count;
379251881Speter
380251881Speter  /* The dictionary, GROUP_SIZE * group_count entries long. Never NULL.
381251881Speter   */
382251881Speter  entry_group_t *directory;
383251881Speter
384251881Speter  /* Flag array with group_count / GROUP_INIT_GRANULARITY _bit_ elements.
385251881Speter   * Allows for efficiently marking groups as "not initialized".
386251881Speter   */
387251881Speter  unsigned char *group_initialized;
388251881Speter
389251881Speter  /* Size of dictionary in groups. Must be > 0.
390251881Speter   */
391251881Speter  apr_uint32_t group_count;
392251881Speter
393251881Speter  /* Reference to the first (defined by the order content in the data
394251881Speter   * buffer) dictionary entry used by any data item.
395251881Speter   * NO_INDEX for an empty cache.
396251881Speter   */
397251881Speter  apr_uint32_t first;
398251881Speter
399251881Speter  /* Reference to the last (defined by the order content in the data
400251881Speter   * buffer) dictionary entry used by any data item.
401251881Speter   * NO_INDEX for an empty cache.
402251881Speter   */
403251881Speter  apr_uint32_t last;
404251881Speter
405251881Speter  /* Reference to the first (defined by the order content in the data
406251881Speter   * buffer) used dictionary entry behind the insertion position
407251881Speter   * (current_data). If NO_INDEX, the data buffer is free starting at the
408251881Speter   * current_data offset.
409251881Speter   */
410251881Speter  apr_uint32_t next;
411251881Speter
412251881Speter
413251881Speter  /* Pointer to the data buffer, data_size bytes long. Never NULL.
414251881Speter   */
415251881Speter  unsigned char *data;
416251881Speter
417251881Speter  /* Size of data buffer in bytes. Must be > 0.
418251881Speter   */
419251881Speter  apr_uint64_t data_size;
420251881Speter
421251881Speter  /* Offset in the data buffer where the next insertion shall occur.
422251881Speter   */
423251881Speter  apr_uint64_t current_data;
424251881Speter
425262253Speter  /* Total number of data buffer bytes in use.
426251881Speter   */
427251881Speter  apr_uint64_t data_used;
428251881Speter
429251881Speter  /* Largest entry size that we would accept.  For total cache sizes
430251881Speter   * less than 4TB (sic!), this is determined by the total cache size.
431251881Speter   */
432251881Speter  apr_uint64_t max_entry_size;
433251881Speter
434251881Speter
435251881Speter  /* Number of used dictionary entries, i.e. number of cached items.
436251881Speter   * In conjunction with hit_count, this is used calculate the average
437251881Speter   * hit count as part of the randomized LFU algorithm.
438251881Speter   */
439251881Speter  apr_uint32_t used_entries;
440251881Speter
441251881Speter  /* Sum of (read) hit counts of all used dictionary entries.
442251881Speter   * In conjunction used_entries used_entries, this is used calculate
443251881Speter   * the average hit count as part of the randomized LFU algorithm.
444251881Speter   */
445251881Speter  apr_uint64_t hit_count;
446251881Speter
447251881Speter
448251881Speter  /* Total number of calls to membuffer_cache_get.
449251881Speter   * Purely statistical information that may be used for profiling.
450251881Speter   */
451251881Speter  apr_uint64_t total_reads;
452251881Speter
453251881Speter  /* Total number of calls to membuffer_cache_set.
454251881Speter   * Purely statistical information that may be used for profiling.
455251881Speter   */
456251881Speter  apr_uint64_t total_writes;
457251881Speter
458251881Speter  /* Total number of hits since the cache's creation.
459251881Speter   * Purely statistical information that may be used for profiling.
460251881Speter   */
461251881Speter  apr_uint64_t total_hits;
462251881Speter
463251881Speter#if APR_HAS_THREADS
464251881Speter  /* A lock for intra-process synchronization to the cache, or NULL if
465251881Speter   * the cache's creator doesn't feel the cache needs to be
466251881Speter   * thread-safe.
467251881Speter   */
468251881Speter  apr_thread_rwlock_t *lock;
469251881Speter
470251881Speter  /* If set, write access will wait until they get exclusive access.
471251881Speter   * Otherwise, they will become no-ops if the segment is currently
472251881Speter   * read-locked.
473251881Speter   */
474251881Speter  svn_boolean_t allow_blocking_writes;
475251881Speter#endif
476251881Speter};
477251881Speter
478251881Speter/* Align integer VALUE to the next ITEM_ALIGNMENT boundary.
479251881Speter */
480251881Speter#define ALIGN_VALUE(value) (((value) + ITEM_ALIGNMENT-1) & -ITEM_ALIGNMENT)
481251881Speter
482251881Speter/* Align POINTER value to the next ITEM_ALIGNMENT boundary.
483251881Speter */
484251881Speter#define ALIGN_POINTER(pointer) ((void*)ALIGN_VALUE((apr_size_t)(char*)(pointer)))
485251881Speter
486251881Speter/* If locking is supported for CACHE, acquire a read lock for it.
487251881Speter */
488251881Speterstatic svn_error_t *
489251881Speterread_lock_cache(svn_membuffer_t *cache)
490251881Speter{
491251881Speter#if APR_HAS_THREADS
492251881Speter  if (cache->lock)
493251881Speter  {
494251881Speter    apr_status_t status = apr_thread_rwlock_rdlock(cache->lock);
495251881Speter    if (status)
496251881Speter      return svn_error_wrap_apr(status, _("Can't lock cache mutex"));
497251881Speter  }
498251881Speter#endif
499251881Speter  return SVN_NO_ERROR;
500251881Speter}
501251881Speter
502251881Speter/* If locking is supported for CACHE, acquire a write lock for it.
503251881Speter */
504251881Speterstatic svn_error_t *
505251881Speterwrite_lock_cache(svn_membuffer_t *cache, svn_boolean_t *success)
506251881Speter{
507251881Speter#if APR_HAS_THREADS
508251881Speter  if (cache->lock)
509251881Speter    {
510251881Speter      apr_status_t status;
511251881Speter      if (cache->allow_blocking_writes)
512251881Speter        {
513251881Speter          status = apr_thread_rwlock_wrlock(cache->lock);
514251881Speter        }
515251881Speter      else
516251881Speter        {
517251881Speter          status = apr_thread_rwlock_trywrlock(cache->lock);
518251881Speter          if (SVN_LOCK_IS_BUSY(status))
519251881Speter            {
520251881Speter              *success = FALSE;
521251881Speter              status = APR_SUCCESS;
522251881Speter            }
523251881Speter        }
524251881Speter
525251881Speter      if (status)
526251881Speter        return svn_error_wrap_apr(status,
527251881Speter                                  _("Can't write-lock cache mutex"));
528251881Speter    }
529251881Speter#endif
530251881Speter  return SVN_NO_ERROR;
531251881Speter}
532251881Speter
533251881Speter/* If locking is supported for CACHE, acquire an unconditional write lock
534251881Speter * for it.
535251881Speter */
536251881Speterstatic svn_error_t *
537251881Speterforce_write_lock_cache(svn_membuffer_t *cache)
538251881Speter{
539251881Speter#if APR_HAS_THREADS
540251881Speter  apr_status_t status = apr_thread_rwlock_wrlock(cache->lock);
541251881Speter  if (status)
542251881Speter    return svn_error_wrap_apr(status,
543251881Speter                              _("Can't write-lock cache mutex"));
544251881Speter#endif
545251881Speter  return SVN_NO_ERROR;
546251881Speter}
547251881Speter
548251881Speter/* If locking is supported for CACHE, release the current lock
549251881Speter * (read or write).
550251881Speter */
551251881Speterstatic svn_error_t *
552251881Speterunlock_cache(svn_membuffer_t *cache, svn_error_t *err)
553251881Speter{
554251881Speter#if APR_HAS_THREADS
555251881Speter  if (cache->lock)
556251881Speter  {
557251881Speter    apr_status_t status = apr_thread_rwlock_unlock(cache->lock);
558251881Speter    if (err)
559251881Speter      return err;
560251881Speter
561251881Speter    if (status)
562251881Speter      return svn_error_wrap_apr(status, _("Can't unlock cache mutex"));
563251881Speter  }
564251881Speter#endif
565251881Speter  return err;
566251881Speter}
567251881Speter
568251881Speter/* If supported, guard the execution of EXPR with a read lock to cache.
569251881Speter * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
570251881Speter */
571251881Speter#define WITH_READ_LOCK(cache, expr)         \
572251881Speterdo {                                        \
573251881Speter  SVN_ERR(read_lock_cache(cache));          \
574251881Speter  SVN_ERR(unlock_cache(cache, (expr)));     \
575251881Speter} while (0)
576251881Speter
577251881Speter/* If supported, guard the execution of EXPR with a write lock to cache.
578251881Speter * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
579251881Speter *
580251881Speter * The write lock process is complicated if we don't allow to wait for
581251881Speter * the lock: If we didn't get the lock, we may still need to remove an
582251881Speter * existing entry for the given key because that content is now stale.
583251881Speter * Once we discovered such an entry, we unconditionally do a blocking
584251881Speter * wait for the write lock.  In case no old content could be found, a
585251881Speter * failing lock attempt is simply a no-op and we exit the macro.
586251881Speter */
587251881Speter#define WITH_WRITE_LOCK(cache, expr)                            \
588251881Speterdo {                                                            \
589251881Speter  svn_boolean_t got_lock = TRUE;                                \
590251881Speter  SVN_ERR(write_lock_cache(cache, &got_lock));                  \
591251881Speter  if (!got_lock)                                                \
592251881Speter    {                                                           \
593251881Speter      svn_boolean_t exists;                                     \
594251881Speter      SVN_ERR(entry_exists(cache, group_index, key, &exists));  \
595251881Speter      if (exists)                                               \
596251881Speter        SVN_ERR(force_write_lock_cache(cache));                 \
597251881Speter      else                                                      \
598251881Speter        break;                                                  \
599251881Speter    }                                                           \
600251881Speter  SVN_ERR(unlock_cache(cache, (expr)));                         \
601251881Speter} while (0)
602251881Speter
603251881Speter/* Resolve a dictionary entry reference, i.e. return the entry
604251881Speter * for the given IDX.
605251881Speter */
606251881Speterstatic APR_INLINE entry_t *
607251881Speterget_entry(svn_membuffer_t *cache, apr_uint32_t idx)
608251881Speter{
609251881Speter  return &cache->directory[idx / GROUP_SIZE].entries[idx % GROUP_SIZE];
610251881Speter}
611251881Speter
612251881Speter/* Get the entry references for the given ENTRY.
613251881Speter */
614251881Speterstatic APR_INLINE apr_uint32_t
615251881Speterget_index(svn_membuffer_t *cache, entry_t *entry)
616251881Speter{
617251881Speter  apr_size_t group_index
618251881Speter    = ((char *)entry - (char *)cache->directory) / sizeof(entry_group_t);
619251881Speter
620251881Speter  return (apr_uint32_t)group_index * GROUP_SIZE
621251881Speter       + (apr_uint32_t)(entry - cache->directory[group_index].entries);
622251881Speter}
623251881Speter
624251881Speter/* Remove the used ENTRY from the CACHE, i.e. make it "unused".
625251881Speter * In contrast to insertion, removal is possible for any entry.
626251881Speter */
627251881Speterstatic void
628251881Speterdrop_entry(svn_membuffer_t *cache, entry_t *entry)
629251881Speter{
630251881Speter  /* the group that ENTRY belongs to plus a number of useful index values
631251881Speter   */
632251881Speter  apr_uint32_t idx = get_index(cache, entry);
633251881Speter  apr_uint32_t group_index = idx / GROUP_SIZE;
634251881Speter  entry_group_t *group = &cache->directory[group_index];
635251881Speter  apr_uint32_t last_in_group = group_index * GROUP_SIZE + group->used - 1;
636251881Speter
637251881Speter  /* Only valid to be called for used entries.
638251881Speter   */
639251881Speter  assert(idx <= last_in_group);
640251881Speter
641251881Speter  /* update global cache usage counters
642251881Speter   */
643251881Speter  cache->used_entries--;
644251881Speter  cache->hit_count -= entry->hit_count;
645251881Speter  cache->data_used -= entry->size;
646251881Speter
647251881Speter  /* extend the insertion window, if the entry happens to border it
648251881Speter   */
649251881Speter  if (idx == cache->next)
650251881Speter    cache->next = entry->next;
651251881Speter  else
652251881Speter    if (entry->next == cache->next)
653251881Speter      {
654251881Speter        /* insertion window starts right behind the entry to remove
655251881Speter         */
656251881Speter        if (entry->previous == NO_INDEX)
657251881Speter          {
658251881Speter            /* remove the first entry -> insertion may start at pos 0, now */
659251881Speter            cache->current_data = 0;
660251881Speter          }
661251881Speter        else
662251881Speter          {
663251881Speter            /* insertion may start right behind the previous entry */
664251881Speter            entry_t *previous = get_entry(cache, entry->previous);
665251881Speter            cache->current_data = ALIGN_VALUE(  previous->offset
666251881Speter                                              + previous->size);
667251881Speter          }
668251881Speter      }
669251881Speter
670251881Speter  /* unlink it from the chain of used entries
671251881Speter   */
672251881Speter  if (entry->previous == NO_INDEX)
673251881Speter    cache->first = entry->next;
674251881Speter  else
675251881Speter    get_entry(cache, entry->previous)->next = entry->next;
676251881Speter
677251881Speter  if (entry->next == NO_INDEX)
678251881Speter    cache->last = entry->previous;
679251881Speter  else
680251881Speter    get_entry(cache, entry->next)->previous = entry->previous;
681251881Speter
682251881Speter  /* Move last entry into hole (if the removed one is not the last used).
683251881Speter   * We need to do this since all used entries are at the beginning of
684251881Speter   * the group's entries array.
685251881Speter   */
686251881Speter  if (idx < last_in_group)
687251881Speter    {
688251881Speter      /* copy the last used entry to the removed entry's index
689251881Speter       */
690251881Speter      *entry = group->entries[group->used-1];
691251881Speter
692251881Speter      /* update foreign links to new index
693251881Speter       */
694251881Speter      if (last_in_group == cache->next)
695251881Speter        cache->next = idx;
696251881Speter
697251881Speter      if (entry->previous == NO_INDEX)
698251881Speter        cache->first = idx;
699251881Speter      else
700251881Speter        get_entry(cache, entry->previous)->next = idx;
701251881Speter
702251881Speter      if (entry->next == NO_INDEX)
703251881Speter        cache->last = idx;
704251881Speter      else
705251881Speter        get_entry(cache, entry->next)->previous = idx;
706251881Speter    }
707251881Speter
708251881Speter  /* Update the number of used entries.
709251881Speter   */
710251881Speter  group->used--;
711251881Speter}
712251881Speter
713251881Speter/* Insert ENTRY into the chain of used dictionary entries. The entry's
714251881Speter * offset and size members must already have been initialized. Also,
715251881Speter * the offset must match the beginning of the insertion window.
716251881Speter */
717251881Speterstatic void
718251881Speterinsert_entry(svn_membuffer_t *cache, entry_t *entry)
719251881Speter{
720251881Speter  /* the group that ENTRY belongs to plus a number of useful index values
721251881Speter   */
722251881Speter  apr_uint32_t idx = get_index(cache, entry);
723251881Speter  apr_uint32_t group_index = idx / GROUP_SIZE;
724251881Speter  entry_group_t *group = &cache->directory[group_index];
725251881Speter  entry_t *next = cache->next == NO_INDEX
726251881Speter                ? NULL
727251881Speter                : get_entry(cache, cache->next);
728251881Speter
729251881Speter  /* The entry must start at the beginning of the insertion window.
730251881Speter   * It must also be the first unused entry in the group.
731251881Speter   */
732251881Speter  assert(entry->offset == cache->current_data);
733251881Speter  assert(idx == group_index * GROUP_SIZE + group->used);
734251881Speter  cache->current_data = ALIGN_VALUE(entry->offset + entry->size);
735251881Speter
736251881Speter  /* update usage counters
737251881Speter   */
738251881Speter  cache->used_entries++;
739251881Speter  cache->data_used += entry->size;
740251881Speter  entry->hit_count = 0;
741251881Speter  group->used++;
742251881Speter
743251881Speter  /* update entry chain
744251881Speter   */
745251881Speter  entry->next = cache->next;
746251881Speter  if (cache->first == NO_INDEX)
747251881Speter    {
748251881Speter      /* insert as the first entry and only in the chain
749251881Speter       */
750251881Speter      entry->previous = NO_INDEX;
751251881Speter      cache->last = idx;
752251881Speter      cache->first = idx;
753251881Speter    }
754251881Speter  else if (next == NULL)
755251881Speter    {
756251881Speter      /* insert as the last entry in the chain.
757251881Speter       * Note that it cannot also be at the beginning of the chain.
758251881Speter       */
759251881Speter      entry->previous = cache->last;
760251881Speter      get_entry(cache, cache->last)->next = idx;
761251881Speter      cache->last = idx;
762251881Speter    }
763251881Speter  else
764251881Speter    {
765251881Speter      /* insert either at the start of a non-empty list or
766251881Speter       * somewhere in the middle
767251881Speter       */
768251881Speter      entry->previous = next->previous;
769251881Speter      next->previous = idx;
770251881Speter
771251881Speter      if (entry->previous != NO_INDEX)
772251881Speter        get_entry(cache, entry->previous)->next = idx;
773251881Speter      else
774251881Speter        cache->first = idx;
775251881Speter    }
776251881Speter
777251881Speter  /* The current insertion position must never point outside our
778251881Speter   * data buffer.
779251881Speter   */
780251881Speter  assert(cache->current_data <= cache->data_size);
781251881Speter}
782251881Speter
783251881Speter/* Map a KEY of 16 bytes to the CACHE and group that shall contain the
784251881Speter * respective item.
785251881Speter */
786251881Speterstatic apr_uint32_t
787251881Speterget_group_index(svn_membuffer_t **cache,
788251881Speter                entry_key_t key)
789251881Speter{
790251881Speter  svn_membuffer_t *segment0 = *cache;
791251881Speter
792251881Speter  /* select the cache segment to use. they have all the same group_count */
793251881Speter  *cache = &segment0[key[0] & (segment0->segment_count -1)];
794251881Speter  return key[1] % segment0->group_count;
795251881Speter}
796251881Speter
797251881Speter/* Reduce the hit count of ENTRY and update the accumulated hit info
798251881Speter * in CACHE accordingly.
799251881Speter */
800251881Speterstatic APR_INLINE void
801251881Speterlet_entry_age(svn_membuffer_t *cache, entry_t *entry)
802251881Speter{
803251881Speter  apr_uint32_t hits_removed = (entry->hit_count + 1) >> 1;
804251881Speter
805251881Speter  cache->hit_count -= hits_removed;
806251881Speter  entry->hit_count -= hits_removed;
807251881Speter}
808251881Speter
809251881Speter/* Returns 0 if the entry group identified by GROUP_INDEX in CACHE has not
810251881Speter * been initialized, yet. In that case, this group can not data. Otherwise,
811251881Speter * a non-zero value is returned.
812251881Speter */
813251881Speterstatic APR_INLINE unsigned char
814251881Speteris_group_initialized(svn_membuffer_t *cache, apr_uint32_t group_index)
815251881Speter{
816251881Speter  unsigned char flags
817251881Speter    = cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)];
818251881Speter  unsigned char bit_mask
819251881Speter    = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
820251881Speter
821251881Speter  return flags & bit_mask;
822251881Speter}
823251881Speter
824251881Speter/* Initializes the section of the directory in CACHE that contains
825251881Speter * the entry group identified by GROUP_INDEX. */
826251881Speterstatic void
827251881Speterinitialize_group(svn_membuffer_t *cache, apr_uint32_t group_index)
828251881Speter{
829251881Speter  unsigned char bit_mask;
830251881Speter  apr_uint32_t i;
831251881Speter
832251881Speter  /* range of groups to initialize due to GROUP_INIT_GRANULARITY */
833251881Speter  apr_uint32_t first_index =
834251881Speter      (group_index / GROUP_INIT_GRANULARITY) * GROUP_INIT_GRANULARITY;
835251881Speter  apr_uint32_t last_index = first_index + GROUP_INIT_GRANULARITY;
836251881Speter  if (last_index > cache->group_count)
837251881Speter    last_index = cache->group_count;
838251881Speter
839251881Speter  for (i = first_index; i < last_index; ++i)
840251881Speter    cache->directory[i].used = 0;
841251881Speter
842251881Speter  /* set the "initialized" bit for these groups */
843251881Speter  bit_mask
844251881Speter    = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
845251881Speter  cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)]
846251881Speter    |= bit_mask;
847251881Speter}
848251881Speter
849251881Speter/* Given the GROUP_INDEX that shall contain an entry with the hash key
850251881Speter * TO_FIND, find that entry in the specified group.
851251881Speter *
852251881Speter * If FIND_EMPTY is not set, this function will return the one used entry
853251881Speter * that actually matches the hash or NULL, if no such entry exists.
854251881Speter *
855251881Speter * If FIND_EMPTY has been set, this function will drop the one used entry
856251881Speter * that actually matches the hash (i.e. make it fit to be replaced with
857251881Speter * new content), an unused entry or a forcibly removed entry (if all
858251881Speter * group entries are currently in use). The entries' hash value will be
859251881Speter * initialized with TO_FIND.
860251881Speter */
861251881Speterstatic entry_t *
862251881Speterfind_entry(svn_membuffer_t *cache,
863251881Speter           apr_uint32_t group_index,
864251881Speter           const apr_uint64_t to_find[2],
865251881Speter           svn_boolean_t find_empty)
866251881Speter{
867251881Speter  entry_group_t *group;
868251881Speter  entry_t *entry = NULL;
869251881Speter  apr_size_t i;
870251881Speter
871251881Speter  /* get the group that *must* contain the entry
872251881Speter   */
873251881Speter  group = &cache->directory[group_index];
874251881Speter
875251881Speter  /* If the entry group has not been initialized, yet, there is no data.
876251881Speter   */
877251881Speter  if (! is_group_initialized(cache, group_index))
878251881Speter    {
879251881Speter      if (find_empty)
880251881Speter        {
881251881Speter          initialize_group(cache, group_index);
882251881Speter          entry = &group->entries[0];
883251881Speter
884251881Speter          /* initialize entry for the new key */
885251881Speter          entry->key[0] = to_find[0];
886251881Speter          entry->key[1] = to_find[1];
887251881Speter        }
888251881Speter
889251881Speter      return entry;
890251881Speter    }
891251881Speter
892251881Speter  /* try to find the matching entry
893251881Speter   */
894251881Speter  for (i = 0; i < group->used; ++i)
895251881Speter    if (   to_find[0] == group->entries[i].key[0]
896251881Speter        && to_find[1] == group->entries[i].key[1])
897251881Speter      {
898251881Speter        /* found it
899251881Speter         */
900251881Speter        entry = &group->entries[i];
901251881Speter        if (find_empty)
902251881Speter          drop_entry(cache, entry);
903251881Speter        else
904251881Speter          return entry;
905251881Speter      }
906251881Speter
907251881Speter  /* None found. Are we looking for a free entry?
908251881Speter   */
909251881Speter  if (find_empty)
910251881Speter    {
911251881Speter      /* if there is no empty entry, delete the oldest entry
912251881Speter       */
913251881Speter      if (group->used == GROUP_SIZE)
914251881Speter        {
915251881Speter          /* every entry gets the same chance of being removed.
916251881Speter           * Otherwise, we free the first entry, fill it and
917251881Speter           * remove it again on the next occasion without considering
918251881Speter           * the other entries in this group.
919251881Speter           */
920251881Speter          entry = &group->entries[rand() % GROUP_SIZE];
921251881Speter          for (i = 1; i < GROUP_SIZE; ++i)
922251881Speter            if (entry->hit_count > group->entries[i].hit_count)
923251881Speter              entry = &group->entries[i];
924251881Speter
925251881Speter          /* for the entries that don't have been removed,
926251881Speter           * reduce their hit counts to put them at a relative
927251881Speter           * disadvantage the next time.
928251881Speter           */
929251881Speter          for (i = 0; i < GROUP_SIZE; ++i)
930251881Speter            if (entry != &group->entries[i])
931251881Speter              let_entry_age(cache, entry);
932251881Speter
933251881Speter          drop_entry(cache, entry);
934251881Speter        }
935251881Speter
936251881Speter      /* initialize entry for the new key
937251881Speter       */
938251881Speter      entry = &group->entries[group->used];
939251881Speter      entry->key[0] = to_find[0];
940251881Speter      entry->key[1] = to_find[1];
941251881Speter    }
942251881Speter
943251881Speter  return entry;
944251881Speter}
945251881Speter
946251881Speter/* Move a surviving ENTRY from just behind the insertion window to
947251881Speter * its beginning and move the insertion window up accordingly.
948251881Speter */
949251881Speterstatic void
950251881Spetermove_entry(svn_membuffer_t *cache, entry_t *entry)
951251881Speter{
952251881Speter  apr_size_t size = ALIGN_VALUE(entry->size);
953251881Speter
954251881Speter  /* This entry survived this cleansing run. Reset half of its
955251881Speter   * hit count so that its removal gets more likely in the next
956251881Speter   * run unless someone read / hit this entry in the meantime.
957251881Speter   */
958251881Speter  let_entry_age(cache, entry);
959251881Speter
960251881Speter  /* Move the entry to the start of the empty / insertion section
961251881Speter   * (if it isn't there already). Size-aligned moves are legal
962251881Speter   * since all offsets and block sizes share this same alignment.
963251881Speter   * Size-aligned moves tend to be faster than non-aligned ones
964251881Speter   * because no "odd" bytes at the end need to special treatment.
965251881Speter   */
966251881Speter  if (entry->offset != cache->current_data)
967251881Speter    {
968251881Speter      memmove(cache->data + cache->current_data,
969251881Speter              cache->data + entry->offset,
970251881Speter              size);
971251881Speter      entry->offset = cache->current_data;
972251881Speter    }
973251881Speter
974251881Speter  /* The insertion position is now directly behind this entry.
975251881Speter   */
976251881Speter  cache->current_data = entry->offset + size;
977251881Speter  cache->next = entry->next;
978251881Speter
979251881Speter  /* The current insertion position must never point outside our
980251881Speter   * data buffer.
981251881Speter   */
982251881Speter  assert(cache->current_data <= cache->data_size);
983251881Speter}
984251881Speter
985251881Speter/* If necessary, enlarge the insertion window until it is at least
986251881Speter * SIZE bytes long. SIZE must not exceed the data buffer size.
987251881Speter * Return TRUE if enough room could be found or made. A FALSE result
988251881Speter * indicates that the respective item shall not be added.
989251881Speter */
990251881Speterstatic svn_boolean_t
991251881Speterensure_data_insertable(svn_membuffer_t *cache, apr_size_t size)
992251881Speter{
993251881Speter  entry_t *entry;
994251881Speter  apr_uint64_t average_hit_value;
995251881Speter  apr_uint64_t threshold;
996251881Speter
997251881Speter  /* accumulated size of the entries that have been removed to make
998251881Speter   * room for the new one.
999251881Speter   */
1000251881Speter  apr_size_t drop_size = 0;
1001251881Speter
1002251881Speter  /* This loop will eventually terminate because every cache entry
1003251881Speter   * would get dropped eventually:
1004251881Speter   * - hit counts become 0 after the got kept for 32 full scans
1005251881Speter   * - larger elements get dropped as soon as their hit count is 0
1006251881Speter   * - smaller and smaller elements get removed as the average
1007251881Speter   *   entry size drops (average drops by a factor of 8 per scan)
1008251881Speter   * - after no more than 43 full scans, all elements would be removed
1009251881Speter   *
1010251881Speter   * Since size is < 4th of the cache size and about 50% of all
1011251881Speter   * entries get removed by a scan, it is very unlikely that more
1012251881Speter   * than a fractional scan will be necessary.
1013251881Speter   */
1014251881Speter  while (1)
1015251881Speter    {
1016251881Speter      /* first offset behind the insertion window
1017251881Speter       */
1018251881Speter      apr_uint64_t end = cache->next == NO_INDEX
1019251881Speter                       ? cache->data_size
1020251881Speter                       : get_entry(cache, cache->next)->offset;
1021251881Speter
1022251881Speter      /* leave function as soon as the insertion window is large enough
1023251881Speter       */
1024251881Speter      if (end >= size + cache->current_data)
1025251881Speter        return TRUE;
1026251881Speter
1027251881Speter      /* Don't be too eager to cache data. Smaller items will fit into
1028251881Speter       * the cache after dropping a single item. Of the larger ones, we
1029251881Speter       * will only accept about 50%. They are also likely to get evicted
1030251881Speter       * soon due to their notoriously low hit counts.
1031251881Speter       *
1032251881Speter       * As long as enough similarly or even larger sized entries already
1033251881Speter       * exist in the cache, much less insert requests will be rejected.
1034251881Speter       */
1035251881Speter      if (2 * drop_size > size)
1036251881Speter        return FALSE;
1037251881Speter
1038251881Speter      /* try to enlarge the insertion window
1039251881Speter       */
1040251881Speter      if (cache->next == NO_INDEX)
1041251881Speter        {
1042251881Speter          /* We reached the end of the data buffer; restart at the beginning.
1043251881Speter           * Due to the randomized nature of our LFU implementation, very
1044251881Speter           * large data items may require multiple passes. Therefore, SIZE
1045251881Speter           * should be restricted to significantly less than data_size.
1046251881Speter           */
1047251881Speter          cache->current_data = 0;
1048251881Speter          cache->next = cache->first;
1049251881Speter        }
1050251881Speter      else
1051251881Speter        {
1052251881Speter          entry = get_entry(cache, cache->next);
1053251881Speter
1054251881Speter          /* Keep entries that are very small. Those are likely to be data
1055251881Speter           * headers or similar management structures. So, they are probably
1056251881Speter           * important while not occupying much space.
1057251881Speter           * But keep them only as long as they are a minority.
1058251881Speter           */
1059251881Speter          if (   (apr_uint64_t)entry->size * cache->used_entries
1060251881Speter               < cache->data_used / 8)
1061251881Speter            {
1062251881Speter              move_entry(cache, entry);
1063251881Speter            }
1064251881Speter          else
1065251881Speter            {
1066251881Speter              svn_boolean_t keep;
1067251881Speter
1068251881Speter              if (cache->hit_count > cache->used_entries)
1069251881Speter                {
1070251881Speter                  /* Roll the dice and determine a threshold somewhere from 0 up
1071251881Speter                   * to 2 times the average hit count.
1072251881Speter                   */
1073251881Speter                  average_hit_value = cache->hit_count / cache->used_entries;
1074251881Speter                  threshold = (average_hit_value+1) * (rand() % 4096) / 2048;
1075251881Speter
1076251881Speter                  keep = entry->hit_count >= threshold;
1077251881Speter                }
1078251881Speter              else
1079251881Speter                {
1080251881Speter                  /* general hit count is low. Keep everything that got hit
1081251881Speter                   * at all and assign some 50% survival chance to everything
1082251881Speter                   * else.
1083251881Speter                   */
1084251881Speter                  keep = (entry->hit_count > 0) || (rand() & 1);
1085251881Speter                }
1086251881Speter
1087251881Speter              /* keepers or destroyers? */
1088251881Speter              if (keep)
1089251881Speter                {
1090251881Speter                  move_entry(cache, entry);
1091251881Speter                }
1092251881Speter              else
1093251881Speter                {
1094251881Speter                 /* Drop the entry from the end of the insertion window, if it
1095251881Speter                  * has been hit less than the threshold. Otherwise, keep it and
1096251881Speter                  * move the insertion window one entry further.
1097251881Speter                  */
1098251881Speter                  drop_size += entry->size;
1099251881Speter                  drop_entry(cache, entry);
1100251881Speter                }
1101251881Speter            }
1102251881Speter        }
1103251881Speter    }
1104251881Speter
1105251881Speter  /* This will never be reached. But if it was, "can't insert" was the
1106251881Speter   * right answer. */
1107251881Speter}
1108251881Speter
1109251881Speter/* Mimic apr_pcalloc in APR_POOL_DEBUG mode, i.e. handle failed allocations
1110251881Speter * (e.g. OOM) properly: Allocate at least SIZE bytes from POOL and zero
1111251881Speter * the content of the allocated memory if ZERO has been set. Return NULL
1112251881Speter * upon failed allocations.
1113251881Speter *
1114251881Speter * Also, satisfy our buffer alignment needs for performance reasons.
1115251881Speter */
1116251881Speterstatic void* secure_aligned_alloc(apr_pool_t *pool,
1117251881Speter                                  apr_size_t size,
1118251881Speter                                  svn_boolean_t zero)
1119251881Speter{
1120251881Speter  void* memory = apr_palloc(pool, size + ITEM_ALIGNMENT);
1121251881Speter  if (memory != NULL)
1122251881Speter    {
1123251881Speter      memory = ALIGN_POINTER(memory);
1124251881Speter      if (zero)
1125251881Speter        memset(memory, 0, size);
1126251881Speter    }
1127251881Speter
1128251881Speter  return memory;
1129251881Speter}
1130251881Speter
1131251881Spetersvn_error_t *
1132251881Spetersvn_cache__membuffer_cache_create(svn_membuffer_t **cache,
1133251881Speter                                  apr_size_t total_size,
1134251881Speter                                  apr_size_t directory_size,
1135251881Speter                                  apr_size_t segment_count,
1136251881Speter                                  svn_boolean_t thread_safe,
1137251881Speter                                  svn_boolean_t allow_blocking_writes,
1138251881Speter                                  apr_pool_t *pool)
1139251881Speter{
1140251881Speter  svn_membuffer_t *c;
1141251881Speter
1142251881Speter  apr_uint32_t seg;
1143251881Speter  apr_uint32_t group_count;
1144251881Speter  apr_uint32_t group_init_size;
1145251881Speter  apr_uint64_t data_size;
1146251881Speter  apr_uint64_t max_entry_size;
1147251881Speter
1148251881Speter  /* Limit the total size (only relevant if we can address > 4GB)
1149251881Speter   */
1150251881Speter#if APR_SIZEOF_VOIDP > 4
1151251881Speter  if (total_size > MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT)
1152251881Speter    total_size = MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT;
1153251881Speter#endif
1154251881Speter
1155251881Speter  /* Limit the segment count
1156251881Speter   */
1157251881Speter  if (segment_count > MAX_SEGMENT_COUNT)
1158251881Speter    segment_count = MAX_SEGMENT_COUNT;
1159251881Speter  if (segment_count * MIN_SEGMENT_SIZE > total_size)
1160251881Speter    segment_count = total_size / MIN_SEGMENT_SIZE;
1161251881Speter
1162251881Speter  /* The segment count must be a power of two. Round it down as necessary.
1163251881Speter   */
1164251881Speter  while ((segment_count & (segment_count-1)) != 0)
1165251881Speter    segment_count &= segment_count-1;
1166251881Speter
1167251881Speter  /* if the caller hasn't provided a reasonable segment count or the above
1168251881Speter   * limitations set it to 0, derive one from the absolute cache size
1169251881Speter   */
1170251881Speter  if (segment_count < 1)
1171251881Speter    {
1172251881Speter      /* Determine a reasonable number of cache segments. Segmentation is
1173251881Speter       * only useful for multi-threaded / multi-core servers as it reduces
1174251881Speter       * lock contention on these systems.
1175251881Speter       *
1176251881Speter       * But on these systems, we can assume that ample memory has been
1177251881Speter       * allocated to this cache. Smaller caches should not be segmented
1178251881Speter       * as this severely limits the maximum size of cachable items.
1179251881Speter       *
1180251881Speter       * Segments should not be smaller than 32MB and max. cachable item
1181251881Speter       * size should grow as fast as segmentation.
1182251881Speter       */
1183251881Speter
1184251881Speter      apr_uint32_t segment_count_shift = 0;
1185251881Speter      while (((2 * DEFAULT_MIN_SEGMENT_SIZE) << (2 * segment_count_shift))
1186251881Speter             < total_size)
1187251881Speter        ++segment_count_shift;
1188251881Speter
1189251881Speter      segment_count = (apr_size_t)1 << segment_count_shift;
1190251881Speter    }
1191251881Speter
1192251881Speter  /* If we have an extremely large cache (>512 GB), the default segment
1193251881Speter   * size may exceed the amount allocatable as one chunk. In that case,
1194251881Speter   * increase segmentation until we are under the threshold.
1195251881Speter   */
1196251881Speter  while (   total_size / segment_count > MAX_SEGMENT_SIZE
1197251881Speter         && segment_count < MAX_SEGMENT_COUNT)
1198251881Speter    segment_count *= 2;
1199251881Speter
1200251881Speter  /* allocate cache as an array of segments / cache objects */
1201251881Speter  c = apr_palloc(pool, segment_count * sizeof(*c));
1202251881Speter
1203251881Speter  /* Split total cache size into segments of equal size
1204251881Speter   */
1205251881Speter  total_size /= segment_count;
1206251881Speter  directory_size /= segment_count;
1207251881Speter
1208251881Speter  /* prevent pathological conditions: ensure a certain minimum cache size
1209251881Speter   */
1210251881Speter  if (total_size < 2 * sizeof(entry_group_t))
1211251881Speter    total_size = 2 * sizeof(entry_group_t);
1212251881Speter
1213251881Speter  /* adapt the dictionary size accordingly, if necessary:
1214251881Speter   * It must hold at least one group and must not exceed the cache size.
1215251881Speter   */
1216251881Speter  if (directory_size > total_size - sizeof(entry_group_t))
1217251881Speter    directory_size = total_size - sizeof(entry_group_t);
1218251881Speter  if (directory_size < sizeof(entry_group_t))
1219251881Speter    directory_size = sizeof(entry_group_t);
1220251881Speter
1221251881Speter  /* limit the data size to what we can address.
1222251881Speter   * Note that this cannot overflow since all values are of size_t.
1223251881Speter   * Also, make it a multiple of the item placement granularity to
1224251881Speter   * prevent subtle overflows.
1225251881Speter   */
1226251881Speter  data_size = ALIGN_VALUE(total_size - directory_size + 1) - ITEM_ALIGNMENT;
1227251881Speter
1228251881Speter  /* For cache sizes > 4TB, individual cache segments will be larger
1229251881Speter   * than 16GB allowing for >4GB entries.  But caching chunks larger
1230251881Speter   * than 4GB is simply not supported.
1231251881Speter   */
1232251881Speter  max_entry_size = data_size / 4 > MAX_ITEM_SIZE
1233251881Speter                 ? MAX_ITEM_SIZE
1234251881Speter                 : data_size / 4;
1235251881Speter
1236251881Speter  /* to keep the entries small, we use 32 bit indexes only
1237251881Speter   * -> we need to ensure that no more then 4G entries exist.
1238251881Speter   *
1239251881Speter   * Note, that this limit could only be exceeded in a very
1240251881Speter   * theoretical setup with about 1EB of cache.
1241251881Speter   */
1242251881Speter  group_count = directory_size / sizeof(entry_group_t)
1243251881Speter                    >= (APR_UINT32_MAX / GROUP_SIZE)
1244251881Speter              ? (APR_UINT32_MAX / GROUP_SIZE) - 1
1245251881Speter              : (apr_uint32_t)(directory_size / sizeof(entry_group_t));
1246251881Speter
1247251881Speter  group_init_size = 1 + group_count / (8 * GROUP_INIT_GRANULARITY);
1248251881Speter  for (seg = 0; seg < segment_count; ++seg)
1249251881Speter    {
1250251881Speter      /* allocate buffers and initialize cache members
1251251881Speter       */
1252251881Speter      c[seg].segment_count = (apr_uint32_t)segment_count;
1253251881Speter
1254251881Speter      c[seg].group_count = group_count;
1255251881Speter      c[seg].directory = apr_pcalloc(pool,
1256251881Speter                                     group_count * sizeof(entry_group_t));
1257251881Speter
1258251881Speter      /* Allocate and initialize directory entries as "not initialized",
1259251881Speter         hence "unused" */
1260251881Speter      c[seg].group_initialized = apr_pcalloc(pool, group_init_size);
1261251881Speter
1262251881Speter      c[seg].first = NO_INDEX;
1263251881Speter      c[seg].last = NO_INDEX;
1264251881Speter      c[seg].next = NO_INDEX;
1265251881Speter
1266251881Speter      c[seg].data_size = data_size;
1267251881Speter      c[seg].data = secure_aligned_alloc(pool, (apr_size_t)data_size, FALSE);
1268251881Speter      c[seg].current_data = 0;
1269251881Speter      c[seg].data_used = 0;
1270251881Speter      c[seg].max_entry_size = max_entry_size;
1271251881Speter
1272251881Speter      c[seg].used_entries = 0;
1273251881Speter      c[seg].hit_count = 0;
1274251881Speter      c[seg].total_reads = 0;
1275251881Speter      c[seg].total_writes = 0;
1276251881Speter      c[seg].total_hits = 0;
1277251881Speter
1278251881Speter      /* were allocations successful?
1279251881Speter       * If not, initialize a minimal cache structure.
1280251881Speter       */
1281251881Speter      if (c[seg].data == NULL || c[seg].directory == NULL)
1282251881Speter        {
1283251881Speter          /* We are OOM. There is no need to proceed with "half a cache".
1284251881Speter           */
1285251881Speter          return svn_error_wrap_apr(APR_ENOMEM, "OOM");
1286251881Speter        }
1287251881Speter
1288251881Speter#if APR_HAS_THREADS
1289251881Speter      /* A lock for intra-process synchronization to the cache, or NULL if
1290251881Speter       * the cache's creator doesn't feel the cache needs to be
1291251881Speter       * thread-safe.
1292251881Speter       */
1293251881Speter      c[seg].lock = NULL;
1294251881Speter      if (thread_safe)
1295251881Speter        {
1296251881Speter          apr_status_t status =
1297251881Speter              apr_thread_rwlock_create(&(c[seg].lock), pool);
1298251881Speter          if (status)
1299251881Speter            return svn_error_wrap_apr(status, _("Can't create cache mutex"));
1300251881Speter        }
1301251881Speter
1302251881Speter      /* Select the behavior of write operations.
1303251881Speter       */
1304251881Speter      c[seg].allow_blocking_writes = allow_blocking_writes;
1305251881Speter#endif
1306251881Speter    }
1307251881Speter
1308251881Speter  /* done here
1309251881Speter   */
1310251881Speter  *cache = c;
1311251881Speter  return SVN_NO_ERROR;
1312251881Speter}
1313251881Speter
1314251881Speter/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1315251881Speter * by the hash value TO_FIND and set *FOUND accordingly.
1316251881Speter *
1317251881Speter * Note: This function requires the caller to serialize access.
1318251881Speter * Don't call it directly, call entry_exists instead.
1319251881Speter */
1320251881Speterstatic svn_error_t *
1321251881Speterentry_exists_internal(svn_membuffer_t *cache,
1322251881Speter                      apr_uint32_t group_index,
1323251881Speter                      entry_key_t to_find,
1324251881Speter                      svn_boolean_t *found)
1325251881Speter{
1326251881Speter  *found = find_entry(cache, group_index, to_find, FALSE) != NULL;
1327251881Speter  return SVN_NO_ERROR;
1328251881Speter}
1329251881Speter
1330251881Speter/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1331251881Speter * by the hash value TO_FIND and set *FOUND accordingly.
1332251881Speter */
1333251881Speterstatic svn_error_t *
1334251881Speterentry_exists(svn_membuffer_t *cache,
1335251881Speter             apr_uint32_t group_index,
1336251881Speter             entry_key_t to_find,
1337251881Speter             svn_boolean_t *found)
1338251881Speter{
1339251881Speter  WITH_READ_LOCK(cache,
1340251881Speter                 entry_exists_internal(cache,
1341251881Speter                                       group_index,
1342251881Speter                                       to_find,
1343251881Speter                                       found));
1344251881Speter
1345251881Speter  return SVN_NO_ERROR;
1346251881Speter}
1347251881Speter
1348251881Speter
1349251881Speter/* Try to insert the serialized item given in BUFFER with SIZE into
1350251881Speter * the group GROUP_INDEX of CACHE and uniquely identify it by hash
1351251881Speter * value TO_FIND.
1352251881Speter *
1353251881Speter * However, there is no guarantee that it will actually be put into
1354251881Speter * the cache. If there is already some data associated with TO_FIND,
1355251881Speter * it will be removed from the cache even if the new data cannot
1356251881Speter * be inserted.
1357251881Speter *
1358251881Speter * Note: This function requires the caller to serialization access.
1359251881Speter * Don't call it directly, call membuffer_cache_get_partial instead.
1360251881Speter */
1361251881Speterstatic svn_error_t *
1362251881Spetermembuffer_cache_set_internal(svn_membuffer_t *cache,
1363251881Speter                             entry_key_t to_find,
1364251881Speter                             apr_uint32_t group_index,
1365251881Speter                             char *buffer,
1366251881Speter                             apr_size_t size,
1367251881Speter                             DEBUG_CACHE_MEMBUFFER_TAG_ARG
1368251881Speter                             apr_pool_t *scratch_pool)
1369251881Speter{
1370251881Speter  /* first, look for a previous entry for the given key */
1371251881Speter  entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
1372251881Speter
1373251881Speter  /* if there is an old version of that entry and the new data fits into
1374251881Speter   * the old spot, just re-use that space. */
1375251881Speter  if (entry && ALIGN_VALUE(entry->size) >= size && buffer)
1376251881Speter    {
1377262253Speter      /* Careful! We need to cast SIZE to the full width of CACHE->DATA_USED
1378262253Speter       * lest we run into trouble with 32 bit underflow *not* treated as a
1379262253Speter       * negative value.
1380262253Speter       */
1381262253Speter      cache->data_used += (apr_uint64_t)size - entry->size;
1382251881Speter      entry->size = size;
1383251881Speter
1384251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1385251881Speter
1386251881Speter      /* Remember original content, type and key (hashes)
1387251881Speter       */
1388251881Speter      SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
1389251881Speter      memcpy(&entry->tag, tag, sizeof(*tag));
1390251881Speter
1391251881Speter#endif
1392251881Speter
1393251881Speter      if (size)
1394251881Speter        memcpy(cache->data + entry->offset, buffer, size);
1395251881Speter
1396251881Speter      cache->total_writes++;
1397251881Speter      return SVN_NO_ERROR;
1398251881Speter    }
1399251881Speter
1400251881Speter  /* if necessary, enlarge the insertion window.
1401251881Speter   */
1402251881Speter  if (   buffer != NULL
1403251881Speter      && cache->max_entry_size >= size
1404251881Speter      && ensure_data_insertable(cache, size))
1405251881Speter    {
1406251881Speter      /* Remove old data for this key, if that exists.
1407251881Speter       * Get an unused entry for the key and and initialize it with
1408251881Speter       * the serialized item's (future) position within data buffer.
1409251881Speter       */
1410251881Speter      entry = find_entry(cache, group_index, to_find, TRUE);
1411251881Speter      entry->size = size;
1412251881Speter      entry->offset = cache->current_data;
1413251881Speter
1414251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1415251881Speter
1416251881Speter      /* Remember original content, type and key (hashes)
1417251881Speter       */
1418251881Speter      SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
1419251881Speter      memcpy(&entry->tag, tag, sizeof(*tag));
1420251881Speter
1421251881Speter#endif
1422251881Speter
1423251881Speter      /* Link the entry properly.
1424251881Speter       */
1425251881Speter      insert_entry(cache, entry);
1426251881Speter
1427251881Speter      /* Copy the serialized item data into the cache.
1428251881Speter       */
1429251881Speter      if (size)
1430251881Speter        memcpy(cache->data + entry->offset, buffer, size);
1431251881Speter
1432251881Speter      cache->total_writes++;
1433251881Speter    }
1434251881Speter  else
1435251881Speter    {
1436251881Speter      /* if there is already an entry for this key, drop it.
1437251881Speter       * Since ensure_data_insertable may have removed entries from
1438251881Speter       * ENTRY's group, re-do the lookup.
1439251881Speter       */
1440251881Speter      entry = find_entry(cache, group_index, to_find, FALSE);
1441251881Speter      if (entry)
1442251881Speter        drop_entry(cache, entry);
1443251881Speter    }
1444251881Speter
1445251881Speter  return SVN_NO_ERROR;
1446251881Speter}
1447251881Speter
1448251881Speter/* Try to insert the ITEM and use the KEY to uniquely identify it.
1449251881Speter * However, there is no guarantee that it will actually be put into
1450251881Speter * the cache. If there is already some data associated to the KEY,
1451251881Speter * it will be removed from the cache even if the new data cannot
1452251881Speter * be inserted.
1453251881Speter *
1454251881Speter * The SERIALIZER is called to transform the ITEM into a single,
1455251881Speter * flat data buffer. Temporary allocations may be done in POOL.
1456251881Speter */
1457251881Speterstatic svn_error_t *
1458251881Spetermembuffer_cache_set(svn_membuffer_t *cache,
1459251881Speter                    entry_key_t key,
1460251881Speter                    void *item,
1461251881Speter                    svn_cache__serialize_func_t serializer,
1462251881Speter                    DEBUG_CACHE_MEMBUFFER_TAG_ARG
1463251881Speter                    apr_pool_t *scratch_pool)
1464251881Speter{
1465251881Speter  apr_uint32_t group_index;
1466251881Speter  void *buffer = NULL;
1467251881Speter  apr_size_t size = 0;
1468251881Speter
1469251881Speter  /* find the entry group that will hold the key.
1470251881Speter   */
1471251881Speter  group_index = get_group_index(&cache, key);
1472251881Speter
1473251881Speter  /* Serialize data data.
1474251881Speter   */
1475251881Speter  if (item)
1476251881Speter    SVN_ERR(serializer(&buffer, &size, item, scratch_pool));
1477251881Speter
1478251881Speter  /* The actual cache data access needs to sync'ed
1479251881Speter   */
1480251881Speter  WITH_WRITE_LOCK(cache,
1481251881Speter                  membuffer_cache_set_internal(cache,
1482251881Speter                                               key,
1483251881Speter                                               group_index,
1484251881Speter                                               buffer,
1485251881Speter                                               size,
1486251881Speter                                               DEBUG_CACHE_MEMBUFFER_TAG
1487251881Speter                                               scratch_pool));
1488251881Speter  return SVN_NO_ERROR;
1489251881Speter}
1490251881Speter
1491251881Speter/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1492251881Speter * by the hash value TO_FIND. If no item has been stored for KEY,
1493251881Speter * *BUFFER will be NULL. Otherwise, return a copy of the serialized
1494251881Speter * data in *BUFFER and return its size in *ITEM_SIZE. Allocations will
1495251881Speter * be done in POOL.
1496251881Speter *
1497251881Speter * Note: This function requires the caller to serialization access.
1498251881Speter * Don't call it directly, call membuffer_cache_get_partial instead.
1499251881Speter */
1500251881Speterstatic svn_error_t *
1501251881Spetermembuffer_cache_get_internal(svn_membuffer_t *cache,
1502251881Speter                             apr_uint32_t group_index,
1503251881Speter                             entry_key_t to_find,
1504251881Speter                             char **buffer,
1505251881Speter                             apr_size_t *item_size,
1506251881Speter                             DEBUG_CACHE_MEMBUFFER_TAG_ARG
1507251881Speter                             apr_pool_t *result_pool)
1508251881Speter{
1509251881Speter  entry_t *entry;
1510251881Speter  apr_size_t size;
1511251881Speter
1512251881Speter  /* The actual cache data access needs to sync'ed
1513251881Speter   */
1514251881Speter  entry = find_entry(cache, group_index, to_find, FALSE);
1515251881Speter  cache->total_reads++;
1516251881Speter  if (entry == NULL)
1517251881Speter    {
1518251881Speter      /* no such entry found.
1519251881Speter       */
1520251881Speter      *buffer = NULL;
1521251881Speter      *item_size = 0;
1522251881Speter
1523251881Speter      return SVN_NO_ERROR;
1524251881Speter    }
1525251881Speter
1526251881Speter  size = ALIGN_VALUE(entry->size);
1527251881Speter  *buffer = ALIGN_POINTER(apr_palloc(result_pool, size + ITEM_ALIGNMENT-1));
1528251881Speter  memcpy(*buffer, (const char*)cache->data + entry->offset, size);
1529251881Speter
1530251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1531251881Speter
1532251881Speter  /* Check for overlapping entries.
1533251881Speter   */
1534251881Speter  SVN_ERR_ASSERT(entry->next == NO_INDEX ||
1535251881Speter                 entry->offset + size
1536251881Speter                    <= get_entry(cache, entry->next)->offset);
1537251881Speter
1538251881Speter  /* Compare original content, type and key (hashes)
1539251881Speter   */
1540251881Speter  SVN_ERR(store_content_part(tag, *buffer, entry->size, result_pool));
1541251881Speter  SVN_ERR(assert_equal_tags(&entry->tag, tag));
1542251881Speter
1543251881Speter#endif
1544251881Speter
1545251881Speter  /* update hit statistics
1546251881Speter   */
1547251881Speter  entry->hit_count++;
1548251881Speter  cache->hit_count++;
1549251881Speter  cache->total_hits++;
1550251881Speter
1551251881Speter  *item_size = entry->size;
1552251881Speter
1553251881Speter  return SVN_NO_ERROR;
1554251881Speter}
1555251881Speter
1556251881Speter/* Look for the *ITEM identified by KEY. If no item has been stored
1557251881Speter * for KEY, *ITEM will be NULL. Otherwise, the DESERIALIZER is called
1558251881Speter * re-construct the proper object from the serialized data.
1559251881Speter * Allocations will be done in POOL.
1560251881Speter */
1561251881Speterstatic svn_error_t *
1562251881Spetermembuffer_cache_get(svn_membuffer_t *cache,
1563251881Speter                    entry_key_t key,
1564251881Speter                    void **item,
1565251881Speter                    svn_cache__deserialize_func_t deserializer,
1566251881Speter                    DEBUG_CACHE_MEMBUFFER_TAG_ARG
1567251881Speter                    apr_pool_t *result_pool)
1568251881Speter{
1569251881Speter  apr_uint32_t group_index;
1570251881Speter  char *buffer;
1571251881Speter  apr_size_t size;
1572251881Speter
1573251881Speter  /* find the entry group that will hold the key.
1574251881Speter   */
1575251881Speter  group_index = get_group_index(&cache, key);
1576251881Speter  WITH_READ_LOCK(cache,
1577251881Speter                 membuffer_cache_get_internal(cache,
1578251881Speter                                              group_index,
1579251881Speter                                              key,
1580251881Speter                                              &buffer,
1581251881Speter                                              &size,
1582251881Speter                                              DEBUG_CACHE_MEMBUFFER_TAG
1583251881Speter                                              result_pool));
1584251881Speter
1585251881Speter  /* re-construct the original data object from its serialized form.
1586251881Speter   */
1587251881Speter  if (buffer == NULL)
1588251881Speter    {
1589251881Speter      *item = NULL;
1590251881Speter      return SVN_NO_ERROR;
1591251881Speter    }
1592251881Speter
1593251881Speter  return deserializer(item, buffer, size, result_pool);
1594251881Speter}
1595251881Speter
1596251881Speter/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1597251881Speter * by the hash value TO_FIND. FOUND indicates whether that entry exists.
1598251881Speter * If not found, *ITEM will be NULL.
1599251881Speter *
1600251881Speter * Otherwise, the DESERIALIZER is called with that entry and the BATON
1601251881Speter * provided and will extract the desired information. The result is set
1602251881Speter * in *ITEM. Allocations will be done in POOL.
1603251881Speter *
1604251881Speter * Note: This function requires the caller to serialization access.
1605251881Speter * Don't call it directly, call membuffer_cache_get_partial instead.
1606251881Speter */
1607251881Speterstatic svn_error_t *
1608251881Spetermembuffer_cache_get_partial_internal(svn_membuffer_t *cache,
1609251881Speter                                     apr_uint32_t group_index,
1610251881Speter                                     entry_key_t to_find,
1611251881Speter                                     void **item,
1612251881Speter                                     svn_boolean_t *found,
1613251881Speter                                     svn_cache__partial_getter_func_t deserializer,
1614251881Speter                                     void *baton,
1615251881Speter                                     DEBUG_CACHE_MEMBUFFER_TAG_ARG
1616251881Speter                                     apr_pool_t *result_pool)
1617251881Speter{
1618251881Speter  entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
1619251881Speter  cache->total_reads++;
1620251881Speter  if (entry == NULL)
1621251881Speter    {
1622251881Speter      *item = NULL;
1623251881Speter      *found = FALSE;
1624251881Speter
1625251881Speter      return SVN_NO_ERROR;
1626251881Speter    }
1627251881Speter  else
1628251881Speter    {
1629251881Speter      *found = TRUE;
1630251881Speter
1631251881Speter      entry->hit_count++;
1632251881Speter      cache->hit_count++;
1633251881Speter      cache->total_hits++;
1634251881Speter
1635251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1636251881Speter
1637251881Speter      /* Check for overlapping entries.
1638251881Speter       */
1639251881Speter      SVN_ERR_ASSERT(entry->next == NO_INDEX ||
1640251881Speter                     entry->offset + entry->size
1641251881Speter                        <= get_entry(cache, entry->next)->offset);
1642251881Speter
1643251881Speter      /* Compare original content, type and key (hashes)
1644251881Speter       */
1645251881Speter      SVN_ERR(store_content_part(tag,
1646251881Speter                                 (const char*)cache->data + entry->offset,
1647251881Speter                                 entry->size,
1648251881Speter                                 result_pool));
1649251881Speter      SVN_ERR(assert_equal_tags(&entry->tag, tag));
1650251881Speter
1651251881Speter#endif
1652251881Speter
1653251881Speter      return deserializer(item,
1654251881Speter                          (const char*)cache->data + entry->offset,
1655251881Speter                          entry->size,
1656251881Speter                          baton,
1657251881Speter                          result_pool);
1658251881Speter    }
1659251881Speter}
1660251881Speter
1661251881Speter/* Look for the cache entry identified by KEY. FOUND indicates
1662251881Speter * whether that entry exists. If not found, *ITEM will be NULL. Otherwise,
1663251881Speter * the DESERIALIZER is called with that entry and the BATON provided
1664251881Speter * and will extract the desired information. The result is set in *ITEM.
1665251881Speter * Allocations will be done in POOL.
1666251881Speter */
1667251881Speterstatic svn_error_t *
1668251881Spetermembuffer_cache_get_partial(svn_membuffer_t *cache,
1669251881Speter                            entry_key_t key,
1670251881Speter                            void **item,
1671251881Speter                            svn_boolean_t *found,
1672251881Speter                            svn_cache__partial_getter_func_t deserializer,
1673251881Speter                            void *baton,
1674251881Speter                            DEBUG_CACHE_MEMBUFFER_TAG_ARG
1675251881Speter                            apr_pool_t *result_pool)
1676251881Speter{
1677251881Speter  apr_uint32_t group_index = get_group_index(&cache, key);
1678251881Speter
1679251881Speter  WITH_READ_LOCK(cache,
1680251881Speter                 membuffer_cache_get_partial_internal
1681251881Speter                     (cache, group_index, key, item, found,
1682251881Speter                      deserializer, baton, DEBUG_CACHE_MEMBUFFER_TAG
1683251881Speter                      result_pool));
1684251881Speter
1685251881Speter  return SVN_NO_ERROR;
1686251881Speter}
1687251881Speter
1688251881Speter/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
1689251881Speter * by the hash value TO_FIND. If no entry has been found, the function
1690251881Speter * returns without modifying the cache.
1691251881Speter *
1692251881Speter * Otherwise, FUNC is called with that entry and the BATON provided
1693251881Speter * and may modify the cache entry. Allocations will be done in POOL.
1694251881Speter *
1695251881Speter * Note: This function requires the caller to serialization access.
1696251881Speter * Don't call it directly, call membuffer_cache_set_partial instead.
1697251881Speter */
1698251881Speterstatic svn_error_t *
1699251881Spetermembuffer_cache_set_partial_internal(svn_membuffer_t *cache,
1700251881Speter                                     apr_uint32_t group_index,
1701251881Speter                                     entry_key_t to_find,
1702251881Speter                                     svn_cache__partial_setter_func_t func,
1703251881Speter                                     void *baton,
1704251881Speter                                     DEBUG_CACHE_MEMBUFFER_TAG_ARG
1705251881Speter                                     apr_pool_t *scratch_pool)
1706251881Speter{
1707251881Speter  /* cache item lookup
1708251881Speter   */
1709251881Speter  entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
1710251881Speter  cache->total_reads++;
1711251881Speter
1712251881Speter  /* this function is a no-op if the item is not in cache
1713251881Speter   */
1714251881Speter  if (entry != NULL)
1715251881Speter    {
1716251881Speter      svn_error_t *err;
1717251881Speter
1718251881Speter      /* access the serialized cache item */
1719251881Speter      char *data = (char*)cache->data + entry->offset;
1720251881Speter      char *orig_data = data;
1721251881Speter      apr_size_t size = entry->size;
1722251881Speter
1723251881Speter      entry->hit_count++;
1724251881Speter      cache->hit_count++;
1725251881Speter      cache->total_writes++;
1726251881Speter
1727251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1728251881Speter
1729251881Speter      /* Check for overlapping entries.
1730251881Speter       */
1731251881Speter      SVN_ERR_ASSERT(entry->next == NO_INDEX ||
1732251881Speter                     entry->offset + size
1733251881Speter                        <= get_entry(cache, entry->next)->offset);
1734251881Speter
1735251881Speter      /* Compare original content, type and key (hashes)
1736251881Speter       */
1737251881Speter      SVN_ERR(store_content_part(tag, data, size, scratch_pool));
1738251881Speter      SVN_ERR(assert_equal_tags(&entry->tag, tag));
1739251881Speter
1740251881Speter#endif
1741251881Speter
1742251881Speter      /* modify it, preferably in-situ.
1743251881Speter       */
1744251881Speter      err = func((void **)&data, &size, baton, scratch_pool);
1745251881Speter
1746251881Speter      if (err)
1747251881Speter        {
1748251881Speter          /* Something somewhere when wrong while FUNC was modifying the
1749251881Speter           * changed item. Thus, it might have become invalid /corrupted.
1750251881Speter           * We better drop that.
1751251881Speter           */
1752251881Speter          drop_entry(cache, entry);
1753251881Speter        }
1754251881Speter      else
1755251881Speter        {
1756251881Speter          /* if the modification caused a re-allocation, we need to remove
1757251881Speter           * the old entry and to copy the new data back into cache.
1758251881Speter           */
1759251881Speter          if (data != orig_data)
1760251881Speter            {
1761251881Speter              /* Remove the old entry and try to make space for the new one.
1762251881Speter               */
1763251881Speter              drop_entry(cache, entry);
1764251881Speter              if (   (cache->max_entry_size >= size)
1765251881Speter                  && ensure_data_insertable(cache, size))
1766251881Speter                {
1767251881Speter                  /* Write the new entry.
1768251881Speter                   */
1769251881Speter                  entry = find_entry(cache, group_index, to_find, TRUE);
1770251881Speter                  entry->size = size;
1771251881Speter                  entry->offset = cache->current_data;
1772251881Speter                  if (size)
1773251881Speter                    memcpy(cache->data + entry->offset, data, size);
1774251881Speter
1775251881Speter                  /* Link the entry properly.
1776251881Speter                   */
1777251881Speter                  insert_entry(cache, entry);
1778251881Speter                }
1779251881Speter            }
1780251881Speter
1781251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1782251881Speter
1783251881Speter          /* Remember original content, type and key (hashes)
1784251881Speter           */
1785251881Speter          SVN_ERR(store_content_part(tag, data, size, scratch_pool));
1786251881Speter          memcpy(&entry->tag, tag, sizeof(*tag));
1787251881Speter
1788251881Speter#endif
1789251881Speter        }
1790251881Speter    }
1791251881Speter
1792251881Speter  return SVN_NO_ERROR;
1793251881Speter}
1794251881Speter
1795251881Speter/* Look for the cache entry identified by KEY. If no entry
1796251881Speter * has been found, the function returns without modifying the cache.
1797251881Speter * Otherwise, FUNC is called with that entry and the BATON provided
1798251881Speter * and may modify the cache entry. Allocations will be done in POOL.
1799251881Speter */
1800251881Speterstatic svn_error_t *
1801251881Spetermembuffer_cache_set_partial(svn_membuffer_t *cache,
1802251881Speter                            entry_key_t key,
1803251881Speter                            svn_cache__partial_setter_func_t func,
1804251881Speter                            void *baton,
1805251881Speter                            DEBUG_CACHE_MEMBUFFER_TAG_ARG
1806251881Speter                            apr_pool_t *scratch_pool)
1807251881Speter{
1808251881Speter  /* cache item lookup
1809251881Speter   */
1810251881Speter  apr_uint32_t group_index = get_group_index(&cache, key);
1811251881Speter  WITH_WRITE_LOCK(cache,
1812251881Speter                  membuffer_cache_set_partial_internal
1813251881Speter                     (cache, group_index, key, func, baton,
1814251881Speter                      DEBUG_CACHE_MEMBUFFER_TAG
1815251881Speter                      scratch_pool));
1816251881Speter
1817251881Speter  /* done here -> unlock the cache
1818251881Speter   */
1819251881Speter  return SVN_NO_ERROR;
1820251881Speter}
1821251881Speter
1822251881Speter/* Implement the svn_cache__t interface on top of a shared membuffer cache.
1823251881Speter *
1824251881Speter * Because membuffer caches tend to be very large, there will be rather few
1825251881Speter * of them (usually only one). Thus, the same instance shall be used as the
1826251881Speter * backend to many application-visible svn_cache__t instances. This should
1827251881Speter * also achieve global resource usage fairness.
1828251881Speter *
1829251881Speter * To accommodate items from multiple resources, the individual keys must be
1830251881Speter * unique over all sources. This is achieved by simply adding a prefix key
1831251881Speter * that unambiguously identifies the item's context (e.g. path to the
1832251881Speter * respective repository). The prefix will be set upon construction of the
1833251881Speter * svn_cache__t instance.
1834251881Speter */
1835251881Speter
1836251881Speter/* Internal cache structure (used in svn_cache__t.cache_internal) basically
1837251881Speter * holding the additional parameters needed to call the respective membuffer
1838251881Speter * functions.
1839251881Speter */
1840251881Spetertypedef struct svn_membuffer_cache_t
1841251881Speter{
1842251881Speter  /* this is where all our data will end up in
1843251881Speter   */
1844251881Speter  svn_membuffer_t *membuffer;
1845251881Speter
1846251881Speter  /* use this conversion function when inserting an item into the memcache
1847251881Speter   */
1848251881Speter  svn_cache__serialize_func_t serializer;
1849251881Speter
1850251881Speter  /* use this conversion function when reading an item from the memcache
1851251881Speter   */
1852251881Speter  svn_cache__deserialize_func_t deserializer;
1853251881Speter
1854251881Speter  /* Prepend this byte sequence to any key passed to us.
1855251881Speter   * This makes (very likely) our keys different from all keys used
1856251881Speter   * by other svn_membuffer_cache_t instances.
1857251881Speter   */
1858251881Speter  entry_key_t prefix;
1859251881Speter
1860251881Speter  /* A copy of the unmodified prefix. It is being used as a user-visible
1861251881Speter   * ID for this cache instance.
1862251881Speter   */
1863251881Speter  const char* full_prefix;
1864251881Speter
1865251881Speter  /* length of the keys that will be passed to us through the
1866251881Speter   * svn_cache_t interface. May be APR_HASH_KEY_STRING.
1867251881Speter   */
1868251881Speter  apr_ssize_t key_len;
1869251881Speter
1870251881Speter  /* Temporary buffer containing the hash key for the current access
1871251881Speter   */
1872251881Speter  entry_key_t combined_key;
1873251881Speter
1874251881Speter  /* a pool for temporary allocations during get() and set()
1875251881Speter   */
1876251881Speter  apr_pool_t *pool;
1877251881Speter
1878251881Speter  /* an internal counter that is used to clear the pool from time to time
1879251881Speter   * but not too frequently.
1880251881Speter   */
1881251881Speter  int alloc_counter;
1882251881Speter
1883251881Speter  /* if enabled, this will serialize the access to this instance.
1884251881Speter   */
1885251881Speter  svn_mutex__t *mutex;
1886251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
1887251881Speter
1888251881Speter  /* Invariant tag info for all items stored by this cache instance.
1889251881Speter   */
1890251881Speter  char prefix_tail[PREFIX_TAIL_LEN];
1891251881Speter
1892251881Speter#endif
1893251881Speter} svn_membuffer_cache_t;
1894251881Speter
1895251881Speter/* After an estimated ALLOCATIONS_PER_POOL_CLEAR allocations, we should
1896251881Speter * clear the svn_membuffer_cache_t.pool to keep memory consumption in check.
1897251881Speter */
1898251881Speter#define ALLOCATIONS_PER_POOL_CLEAR 10
1899251881Speter
1900251881Speter
1901251881Speter/* Basically calculate a hash value for KEY of length KEY_LEN, combine it
1902251881Speter * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY.
1903251881Speter */
1904251881Speterstatic void
1905251881Spetercombine_key(svn_membuffer_cache_t *cache,
1906251881Speter            const void *key,
1907251881Speter            apr_ssize_t key_len)
1908251881Speter{
1909251881Speter  if (key_len == APR_HASH_KEY_STRING)
1910251881Speter    key_len = strlen((const char *) key);
1911251881Speter
1912251881Speter  if (key_len < 16)
1913251881Speter    {
1914251881Speter      apr_uint32_t data[4] = { 0 };
1915251881Speter      memcpy(data, key, key_len);
1916251881Speter
1917251881Speter      svn__pseudo_md5_15((apr_uint32_t *)cache->combined_key, data);
1918251881Speter    }
1919251881Speter  else if (key_len < 32)
1920251881Speter    {
1921251881Speter      apr_uint32_t data[8] = { 0 };
1922251881Speter      memcpy(data, key, key_len);
1923251881Speter
1924251881Speter      svn__pseudo_md5_31((apr_uint32_t *)cache->combined_key, data);
1925251881Speter    }
1926251881Speter  else if (key_len < 64)
1927251881Speter    {
1928251881Speter      apr_uint32_t data[16] = { 0 };
1929251881Speter      memcpy(data, key, key_len);
1930251881Speter
1931251881Speter      svn__pseudo_md5_63((apr_uint32_t *)cache->combined_key, data);
1932251881Speter    }
1933251881Speter  else
1934251881Speter    {
1935251881Speter      apr_md5((unsigned char*)cache->combined_key, key, key_len);
1936251881Speter    }
1937251881Speter
1938251881Speter  cache->combined_key[0] ^= cache->prefix[0];
1939251881Speter  cache->combined_key[1] ^= cache->prefix[1];
1940251881Speter}
1941251881Speter
1942251881Speter/* Implement svn_cache__vtable_t.get (not thread-safe)
1943251881Speter */
1944251881Speterstatic svn_error_t *
1945251881Spetersvn_membuffer_cache_get(void **value_p,
1946251881Speter                        svn_boolean_t *found,
1947251881Speter                        void *cache_void,
1948251881Speter                        const void *key,
1949251881Speter                        apr_pool_t *result_pool)
1950251881Speter{
1951251881Speter  svn_membuffer_cache_t *cache = cache_void;
1952251881Speter
1953251881Speter  DEBUG_CACHE_MEMBUFFER_INIT_TAG
1954251881Speter
1955251881Speter  /* special case */
1956251881Speter  if (key == NULL)
1957251881Speter    {
1958251881Speter      *value_p = NULL;
1959251881Speter      *found = FALSE;
1960251881Speter
1961251881Speter      return SVN_NO_ERROR;
1962251881Speter    }
1963251881Speter
1964251881Speter  /* construct the full, i.e. globally unique, key by adding
1965251881Speter   * this cache instances' prefix
1966251881Speter   */
1967251881Speter  combine_key(cache, key, cache->key_len);
1968251881Speter
1969251881Speter  /* Look the item up. */
1970251881Speter  SVN_ERR(membuffer_cache_get(cache->membuffer,
1971251881Speter                              cache->combined_key,
1972251881Speter                              value_p,
1973251881Speter                              cache->deserializer,
1974251881Speter                              DEBUG_CACHE_MEMBUFFER_TAG
1975251881Speter                              result_pool));
1976251881Speter
1977251881Speter  /* return result */
1978251881Speter  *found = *value_p != NULL;
1979251881Speter  return SVN_NO_ERROR;
1980251881Speter}
1981251881Speter
1982251881Speter/* Implement svn_cache__vtable_t.set (not thread-safe)
1983251881Speter */
1984251881Speterstatic svn_error_t *
1985251881Spetersvn_membuffer_cache_set(void *cache_void,
1986251881Speter                        const void *key,
1987251881Speter                        void *value,
1988251881Speter                        apr_pool_t *scratch_pool)
1989251881Speter{
1990251881Speter  svn_membuffer_cache_t *cache = cache_void;
1991251881Speter
1992251881Speter  DEBUG_CACHE_MEMBUFFER_INIT_TAG
1993251881Speter
1994251881Speter  /* special case */
1995251881Speter  if (key == NULL)
1996251881Speter    return SVN_NO_ERROR;
1997251881Speter
1998251881Speter  /* we do some allocations below, so increase the allocation counter
1999251881Speter   * by a slightly larger amount. Free allocated memory every now and then.
2000251881Speter   */
2001251881Speter  cache->alloc_counter += 3;
2002251881Speter  if (cache->alloc_counter > ALLOCATIONS_PER_POOL_CLEAR)
2003251881Speter    {
2004251881Speter      svn_pool_clear(cache->pool);
2005251881Speter      cache->alloc_counter = 0;
2006251881Speter    }
2007251881Speter
2008251881Speter  /* construct the full, i.e. globally unique, key by adding
2009251881Speter   * this cache instances' prefix
2010251881Speter   */
2011251881Speter  combine_key(cache, key, cache->key_len);
2012251881Speter
2013251881Speter  /* (probably) add the item to the cache. But there is no real guarantee
2014251881Speter   * that the item will actually be cached afterwards.
2015251881Speter   */
2016251881Speter  return membuffer_cache_set(cache->membuffer,
2017251881Speter                             cache->combined_key,
2018251881Speter                             value,
2019251881Speter                             cache->serializer,
2020251881Speter                             DEBUG_CACHE_MEMBUFFER_TAG
2021251881Speter                             cache->pool);
2022251881Speter}
2023251881Speter
2024251881Speter/* Implement svn_cache__vtable_t.iter as "not implemented"
2025251881Speter */
2026251881Speterstatic svn_error_t *
2027251881Spetersvn_membuffer_cache_iter(svn_boolean_t *completed,
2028251881Speter                          void *cache_void,
2029251881Speter                          svn_iter_apr_hash_cb_t user_cb,
2030251881Speter                          void *user_baton,
2031251881Speter                          apr_pool_t *scratch_pool)
2032251881Speter{
2033251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2034251881Speter                          _("Can't iterate a membuffer-based cache"));
2035251881Speter}
2036251881Speter
2037251881Speter/* Implement svn_cache__vtable_t.get_partial (not thread-safe)
2038251881Speter */
2039251881Speterstatic svn_error_t *
2040251881Spetersvn_membuffer_cache_get_partial(void **value_p,
2041251881Speter                                svn_boolean_t *found,
2042251881Speter                                void *cache_void,
2043251881Speter                                const void *key,
2044251881Speter                                svn_cache__partial_getter_func_t func,
2045251881Speter                                void *baton,
2046251881Speter                                apr_pool_t *result_pool)
2047251881Speter{
2048251881Speter  svn_membuffer_cache_t *cache = cache_void;
2049251881Speter
2050251881Speter  DEBUG_CACHE_MEMBUFFER_INIT_TAG
2051251881Speter
2052251881Speter  if (key == NULL)
2053251881Speter    {
2054251881Speter      *value_p = NULL;
2055251881Speter      *found = FALSE;
2056251881Speter
2057251881Speter      return SVN_NO_ERROR;
2058251881Speter    }
2059251881Speter
2060251881Speter  combine_key(cache, key, cache->key_len);
2061251881Speter  SVN_ERR(membuffer_cache_get_partial(cache->membuffer,
2062251881Speter                                      cache->combined_key,
2063251881Speter                                      value_p,
2064251881Speter                                      found,
2065251881Speter                                      func,
2066251881Speter                                      baton,
2067251881Speter                                      DEBUG_CACHE_MEMBUFFER_TAG
2068251881Speter                                      result_pool));
2069251881Speter
2070251881Speter  return SVN_NO_ERROR;
2071251881Speter}
2072251881Speter
2073251881Speter/* Implement svn_cache__vtable_t.set_partial (not thread-safe)
2074251881Speter */
2075251881Speterstatic svn_error_t *
2076251881Spetersvn_membuffer_cache_set_partial(void *cache_void,
2077251881Speter                                const void *key,
2078251881Speter                                svn_cache__partial_setter_func_t func,
2079251881Speter                                void *baton,
2080251881Speter                                apr_pool_t *scratch_pool)
2081251881Speter{
2082251881Speter  svn_membuffer_cache_t *cache = cache_void;
2083251881Speter
2084251881Speter  DEBUG_CACHE_MEMBUFFER_INIT_TAG
2085251881Speter
2086251881Speter  if (key != NULL)
2087251881Speter    {
2088251881Speter      combine_key(cache, key, cache->key_len);
2089251881Speter      SVN_ERR(membuffer_cache_set_partial(cache->membuffer,
2090251881Speter                                          cache->combined_key,
2091251881Speter                                          func,
2092251881Speter                                          baton,
2093251881Speter                                          DEBUG_CACHE_MEMBUFFER_TAG
2094251881Speter                                          scratch_pool));
2095251881Speter    }
2096251881Speter  return SVN_NO_ERROR;
2097251881Speter}
2098251881Speter
2099251881Speter/* Implement svn_cache__vtable_t.is_cachable
2100251881Speter * (thread-safe even without mutex)
2101251881Speter */
2102251881Speterstatic svn_boolean_t
2103251881Spetersvn_membuffer_cache_is_cachable(void *cache_void, apr_size_t size)
2104251881Speter{
2105251881Speter  /* Don't allow extremely large element sizes. Otherwise, the cache
2106251881Speter   * might by thrashed by a few extremely large entries. And the size
2107251881Speter   * must be small enough to be stored in a 32 bit value.
2108251881Speter   */
2109251881Speter  svn_membuffer_cache_t *cache = cache_void;
2110251881Speter  return size <= cache->membuffer->max_entry_size;
2111251881Speter}
2112251881Speter
2113251881Speter/* Add statistics of SEGMENT to INFO.
2114251881Speter */
2115251881Speterstatic svn_error_t *
2116251881Spetersvn_membuffer_get_segment_info(svn_membuffer_t *segment,
2117251881Speter                               svn_cache__info_t *info)
2118251881Speter{
2119251881Speter  info->data_size += segment->data_size;
2120251881Speter  info->used_size += segment->data_used;
2121251881Speter  info->total_size += segment->data_size +
2122251881Speter      segment->group_count * GROUP_SIZE * sizeof(entry_t);
2123251881Speter
2124251881Speter  info->used_entries += segment->used_entries;
2125251881Speter  info->total_entries += segment->group_count * GROUP_SIZE;
2126251881Speter
2127251881Speter  return SVN_NO_ERROR;
2128251881Speter}
2129251881Speter
2130251881Speter/* Implement svn_cache__vtable_t.get_info
2131251881Speter * (thread-safe even without mutex)
2132251881Speter */
2133251881Speterstatic svn_error_t *
2134251881Spetersvn_membuffer_cache_get_info(void *cache_void,
2135251881Speter                             svn_cache__info_t *info,
2136251881Speter                             svn_boolean_t reset,
2137251881Speter                             apr_pool_t *result_pool)
2138251881Speter{
2139251881Speter  svn_membuffer_cache_t *cache = cache_void;
2140251881Speter  apr_uint32_t i;
2141251881Speter
2142251881Speter  /* cache front-end specific data */
2143251881Speter
2144251881Speter  info->id = apr_pstrdup(result_pool, cache->full_prefix);
2145251881Speter
2146251881Speter  /* collect info from shared cache back-end */
2147251881Speter
2148251881Speter  info->data_size = 0;
2149251881Speter  info->used_size = 0;
2150251881Speter  info->total_size = 0;
2151251881Speter
2152251881Speter  info->used_entries = 0;
2153251881Speter  info->total_entries = 0;
2154251881Speter
2155251881Speter  for (i = 0; i < cache->membuffer->segment_count; ++i)
2156251881Speter    {
2157251881Speter      svn_membuffer_t *segment = cache->membuffer + i;
2158251881Speter      WITH_READ_LOCK(segment,
2159251881Speter                     svn_membuffer_get_segment_info(segment, info));
2160251881Speter    }
2161251881Speter
2162251881Speter  return SVN_NO_ERROR;
2163251881Speter}
2164251881Speter
2165251881Speter
2166251881Speter/* the v-table for membuffer-based caches (single-threaded access)
2167251881Speter */
2168251881Speterstatic svn_cache__vtable_t membuffer_cache_vtable = {
2169251881Speter  svn_membuffer_cache_get,
2170251881Speter  svn_membuffer_cache_set,
2171251881Speter  svn_membuffer_cache_iter,
2172251881Speter  svn_membuffer_cache_is_cachable,
2173251881Speter  svn_membuffer_cache_get_partial,
2174251881Speter  svn_membuffer_cache_set_partial,
2175251881Speter  svn_membuffer_cache_get_info
2176251881Speter};
2177251881Speter
2178251881Speter/* Implement svn_cache__vtable_t.get and serialize all cache access.
2179251881Speter */
2180251881Speterstatic svn_error_t *
2181251881Spetersvn_membuffer_cache_get_synced(void **value_p,
2182251881Speter                               svn_boolean_t *found,
2183251881Speter                               void *cache_void,
2184251881Speter                               const void *key,
2185251881Speter                               apr_pool_t *result_pool)
2186251881Speter{
2187251881Speter  svn_membuffer_cache_t *cache = cache_void;
2188251881Speter  SVN_MUTEX__WITH_LOCK(cache->mutex,
2189251881Speter                       svn_membuffer_cache_get(value_p,
2190251881Speter                                               found,
2191251881Speter                                               cache_void,
2192251881Speter                                               key,
2193251881Speter                                               result_pool));
2194251881Speter
2195251881Speter  return SVN_NO_ERROR;
2196251881Speter}
2197251881Speter
2198251881Speter/* Implement svn_cache__vtable_t.set and serialize all cache access.
2199251881Speter */
2200251881Speterstatic svn_error_t *
2201251881Spetersvn_membuffer_cache_set_synced(void *cache_void,
2202251881Speter                               const void *key,
2203251881Speter                               void *value,
2204251881Speter                               apr_pool_t *scratch_pool)
2205251881Speter{
2206251881Speter  svn_membuffer_cache_t *cache = cache_void;
2207251881Speter  SVN_MUTEX__WITH_LOCK(cache->mutex,
2208251881Speter                       svn_membuffer_cache_set(cache_void,
2209251881Speter                                               key,
2210251881Speter                                               value,
2211251881Speter                                               scratch_pool));
2212251881Speter
2213251881Speter  return SVN_NO_ERROR;
2214251881Speter}
2215251881Speter
2216251881Speter/* Implement svn_cache__vtable_t.get_partial and serialize all cache access.
2217251881Speter */
2218251881Speterstatic svn_error_t *
2219251881Spetersvn_membuffer_cache_get_partial_synced(void **value_p,
2220251881Speter                                       svn_boolean_t *found,
2221251881Speter                                       void *cache_void,
2222251881Speter                                       const void *key,
2223251881Speter                                       svn_cache__partial_getter_func_t func,
2224251881Speter                                       void *baton,
2225251881Speter                                       apr_pool_t *result_pool)
2226251881Speter{
2227251881Speter  svn_membuffer_cache_t *cache = cache_void;
2228251881Speter  SVN_MUTEX__WITH_LOCK(cache->mutex,
2229251881Speter                       svn_membuffer_cache_get_partial(value_p,
2230251881Speter                                                       found,
2231251881Speter                                                       cache_void,
2232251881Speter                                                       key,
2233251881Speter                                                       func,
2234251881Speter                                                       baton,
2235251881Speter                                                       result_pool));
2236251881Speter
2237251881Speter  return SVN_NO_ERROR;
2238251881Speter}
2239251881Speter
2240251881Speter/* Implement svn_cache__vtable_t.set_partial and serialize all cache access.
2241251881Speter */
2242251881Speterstatic svn_error_t *
2243251881Spetersvn_membuffer_cache_set_partial_synced(void *cache_void,
2244251881Speter                                       const void *key,
2245251881Speter                                       svn_cache__partial_setter_func_t func,
2246251881Speter                                       void *baton,
2247251881Speter                                       apr_pool_t *scratch_pool)
2248251881Speter{
2249251881Speter  svn_membuffer_cache_t *cache = cache_void;
2250251881Speter  SVN_MUTEX__WITH_LOCK(cache->mutex,
2251251881Speter                       svn_membuffer_cache_set_partial(cache_void,
2252251881Speter                                                       key,
2253251881Speter                                                       func,
2254251881Speter                                                       baton,
2255251881Speter                                                       scratch_pool));
2256251881Speter
2257251881Speter  return SVN_NO_ERROR;
2258251881Speter}
2259251881Speter
2260251881Speter/* the v-table for membuffer-based caches with multi-threading support)
2261251881Speter */
2262251881Speterstatic svn_cache__vtable_t membuffer_cache_synced_vtable = {
2263251881Speter  svn_membuffer_cache_get_synced,
2264251881Speter  svn_membuffer_cache_set_synced,
2265251881Speter  svn_membuffer_cache_iter,               /* no sync required */
2266251881Speter  svn_membuffer_cache_is_cachable,        /* no sync required */
2267251881Speter  svn_membuffer_cache_get_partial_synced,
2268251881Speter  svn_membuffer_cache_set_partial_synced,
2269251881Speter  svn_membuffer_cache_get_info            /* no sync required */
2270251881Speter};
2271251881Speter
2272251881Speter/* standard serialization function for svn_stringbuf_t items.
2273251881Speter * Implements svn_cache__serialize_func_t.
2274251881Speter */
2275251881Speterstatic svn_error_t *
2276251881Speterserialize_svn_stringbuf(void **buffer,
2277251881Speter                        apr_size_t *buffer_size,
2278251881Speter                        void *item,
2279251881Speter                        apr_pool_t *result_pool)
2280251881Speter{
2281251881Speter  svn_stringbuf_t *value_str = item;
2282251881Speter
2283251881Speter  *buffer = value_str->data;
2284251881Speter  *buffer_size = value_str->len + 1;
2285251881Speter
2286251881Speter  return SVN_NO_ERROR;
2287251881Speter}
2288251881Speter
2289251881Speter/* standard de-serialization function for svn_stringbuf_t items.
2290251881Speter * Implements svn_cache__deserialize_func_t.
2291251881Speter */
2292251881Speterstatic svn_error_t *
2293251881Speterdeserialize_svn_stringbuf(void **item,
2294251881Speter                          void *buffer,
2295251881Speter                          apr_size_t buffer_size,
2296251881Speter                          apr_pool_t *result_pool)
2297251881Speter{
2298251881Speter  svn_stringbuf_t *value_str = apr_palloc(result_pool, sizeof(svn_stringbuf_t));
2299251881Speter
2300251881Speter  value_str->pool = result_pool;
2301251881Speter  value_str->blocksize = buffer_size;
2302251881Speter  value_str->data = buffer;
2303251881Speter  value_str->len = buffer_size-1;
2304251881Speter  *item = value_str;
2305251881Speter
2306251881Speter  return SVN_NO_ERROR;
2307251881Speter}
2308251881Speter
2309251881Speter/* Construct a svn_cache__t object on top of a shared memcache.
2310251881Speter */
2311251881Spetersvn_error_t *
2312251881Spetersvn_cache__create_membuffer_cache(svn_cache__t **cache_p,
2313251881Speter                                  svn_membuffer_t *membuffer,
2314251881Speter                                  svn_cache__serialize_func_t serializer,
2315251881Speter                                  svn_cache__deserialize_func_t deserializer,
2316251881Speter                                  apr_ssize_t klen,
2317251881Speter                                  const char *prefix,
2318251881Speter                                  svn_boolean_t thread_safe,
2319251881Speter                                  apr_pool_t *pool)
2320251881Speter{
2321251881Speter  svn_checksum_t *checksum;
2322251881Speter
2323251881Speter  /* allocate the cache header structures
2324251881Speter   */
2325251881Speter  svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
2326251881Speter  svn_membuffer_cache_t *cache = apr_palloc(pool, sizeof(*cache));
2327251881Speter
2328251881Speter  /* initialize our internal cache header
2329251881Speter   */
2330251881Speter  cache->membuffer = membuffer;
2331251881Speter  cache->serializer = serializer
2332251881Speter                    ? serializer
2333251881Speter                    : serialize_svn_stringbuf;
2334251881Speter  cache->deserializer = deserializer
2335251881Speter                      ? deserializer
2336251881Speter                      : deserialize_svn_stringbuf;
2337251881Speter  cache->full_prefix = apr_pstrdup(pool, prefix);
2338251881Speter  cache->key_len = klen;
2339251881Speter  cache->pool = svn_pool_create(pool);
2340251881Speter  cache->alloc_counter = 0;
2341251881Speter
2342251881Speter  SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
2343251881Speter
2344251881Speter  /* for performance reasons, we don't actually store the full prefix but a
2345251881Speter   * hash value of it
2346251881Speter   */
2347251881Speter  SVN_ERR(svn_checksum(&checksum,
2348251881Speter                       svn_checksum_md5,
2349251881Speter                       prefix,
2350251881Speter                       strlen(prefix),
2351251881Speter                       pool));
2352251881Speter  memcpy(cache->prefix, checksum->digest, sizeof(cache->prefix));
2353251881Speter
2354251881Speter#ifdef SVN_DEBUG_CACHE_MEMBUFFER
2355251881Speter
2356251881Speter  /* Initialize cache debugging support.
2357251881Speter   */
2358251881Speter  get_prefix_tail(prefix, cache->prefix_tail);
2359251881Speter
2360251881Speter#endif
2361251881Speter
2362251881Speter  /* initialize the generic cache wrapper
2363251881Speter   */
2364251881Speter  wrapper->vtable = thread_safe ? &membuffer_cache_synced_vtable
2365251881Speter                                : &membuffer_cache_vtable;
2366251881Speter  wrapper->cache_internal = cache;
2367251881Speter  wrapper->error_handler = 0;
2368251881Speter  wrapper->error_baton = 0;
2369251881Speter
2370251881Speter  *cache_p = wrapper;
2371251881Speter  return SVN_NO_ERROR;
2372251881Speter}
2373251881Speter
2374