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