2 * cache-inprocess.c: in-memory caching for Subversion
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #include <apr_thread_mutex.h>
28 #include "svn_pools.h"
30 #include "svn_private_config.h"
33 #include "private/svn_mutex.h"
35 /* The (internal) cache object. */
36 typedef struct inprocess_cache_t {
37 /* A user-defined identifier for this cache instance. */
40 /* HASH maps a key (of size KLEN) to a struct cache_entry. */
44 /* Used to copy values into the cache. */
45 svn_cache__serialize_func_t serialize_func;
47 /* Used to copy values out of the cache. */
48 svn_cache__deserialize_func_t deserialize_func;
50 /* Maximum number of pages that this cache instance may allocate */
51 apr_uint64_t total_pages;
52 /* The number of pages we're allowed to allocate before having to
53 * try to reuse one. */
54 apr_uint64_t unallocated_pages;
55 /* Number of cache entries stored on each page. Must be at least 1. */
56 apr_uint64_t items_per_page;
58 /* A dummy cache_page serving as the head of a circular doubly
59 * linked list of cache_pages. SENTINEL->NEXT is the most recently
60 * used page, and SENTINEL->PREV is the least recently used page.
61 * All pages in this list are "full"; the page currently being
62 * filled (PARTIAL_PAGE) is not in the list. */
63 struct cache_page *sentinel;
65 /* A page currently being filled with entries, or NULL if there's no
66 * partially-filled page. This page is not in SENTINEL's list. */
67 struct cache_page *partial_page;
68 /* If PARTIAL_PAGE is not null, this is the number of entries
69 * currently on PARTIAL_PAGE. */
70 apr_uint64_t partial_page_number_filled;
72 /* The pool that the svn_cache__t itself, HASH, and all pages are
73 * allocated in; subpools of this pool are used for the cache_entry
74 * structs, as well as the dup'd values and hash keys.
76 apr_pool_t *cache_pool;
78 /* Sum of the SIZE members of all cache_entry elements that are
79 * accessible from HASH. This is used to make statistics available
80 * even if the sub-pools have already been destroyed.
84 /* A lock for intra-process synchronization to the cache, or NULL if
85 * the cache's creator doesn't feel the cache needs to be
90 /* A cache page; all items on the page are allocated from the same
93 /* Pointers for the LRU list anchored at the cache's SENTINEL.
94 * (NULL for the PARTIAL_PAGE.) */
95 struct cache_page *prev;
96 struct cache_page *next;
98 /* The pool in which cache_entry structs, hash keys, and dup'd
99 * values are allocated. The CACHE_PAGE structs are allocated
100 * in CACHE_POOL and have the same lifetime as the cache itself.
101 * (The cache will never allocate more than TOTAL_PAGES page
102 * structs (inclusive of the sentinel) from CACHE_POOL.)
104 apr_pool_t *page_pool;
106 /* A singly linked list of the entries on this page; used to remove
107 * them from the cache's HASH before reusing the page. */
108 struct cache_entry *first_entry;
111 /* An cache entry. */
115 /* serialized value */
118 /* length of the serialized value in bytes */
121 /* The page it's on (needed so that the LRU list can be
123 struct cache_page *page;
125 /* Next entry on the page. */
126 struct cache_entry *next_entry;
130 /* Removes PAGE from the doubly-linked list it is in (leaving its PREV
131 * and NEXT fields undefined). */
133 remove_page_from_list(struct cache_page *page)
135 page->prev->next = page->next;
136 page->next->prev = page->prev;
139 /* Inserts PAGE after CACHE's sentinel. */
141 insert_page(inprocess_cache_t *cache,
142 struct cache_page *page)
144 struct cache_page *pred = cache->sentinel;
147 page->next = pred->next;
148 page->prev->next = page;
149 page->next->prev = page;
152 /* If PAGE is in the circularly linked list (eg, its NEXT isn't NULL),
153 * move it to the front of the list. */
155 move_page_to_front(inprocess_cache_t *cache,
156 struct cache_page *page)
158 /* This function is called whilst CACHE is locked. */
160 SVN_ERR_ASSERT(page != cache->sentinel);
165 remove_page_from_list(page);
166 insert_page(cache, page);
171 /* Return a copy of KEY inside POOL, using CACHE->KLEN to figure out
174 duplicate_key(inprocess_cache_t *cache,
178 if (cache->klen == APR_HASH_KEY_STRING)
179 return apr_pstrdup(pool, key);
181 return apr_pmemdup(pool, key, cache->klen);
185 inprocess_cache_get_internal(char **buffer,
187 inprocess_cache_t *cache,
189 apr_pool_t *result_pool)
191 struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
196 SVN_ERR(move_page_to_front(cache, entry->page));
198 /* duplicate the buffer entry */
199 *buffer = apr_palloc(result_pool, entry->size);
200 memcpy(*buffer, entry->value, entry->size);
209 inprocess_cache_get(void **value_p,
210 svn_boolean_t *found,
213 apr_pool_t *result_pool)
215 inprocess_cache_t *cache = cache_void;
220 SVN_MUTEX__WITH_LOCK(cache->mutex,
221 inprocess_cache_get_internal(&buffer,
227 /* deserialize the buffer content. Usually, this will directly
228 modify the buffer content directly.
231 *found = buffer != NULL;
232 return buffer && size
233 ? cache->deserialize_func(value_p, buffer, size, result_pool)
237 /* Removes PAGE from the LRU list, removes all of its entries from
238 * CACHE's hash, clears its pool, and sets its entry pointer to NULL.
239 * Finally, puts it in the "partial page" slot in the cache and sets
240 * partial_page_number_filled to 0. Must be called on a page actually
243 erase_page(inprocess_cache_t *cache,
244 struct cache_page *page)
246 struct cache_entry *e;
248 remove_page_from_list(page);
250 for (e = page->first_entry;
254 cache->data_size -= e->size;
255 apr_hash_set(cache->hash, e->key, cache->klen, NULL);
258 svn_pool_clear(page->page_pool);
260 page->first_entry = NULL;
264 cache->partial_page = page;
265 cache->partial_page_number_filled = 0;
270 inprocess_cache_set_internal(inprocess_cache_t *cache,
273 apr_pool_t *scratch_pool)
275 struct cache_entry *existing_entry;
277 existing_entry = apr_hash_get(cache->hash, key, cache->klen);
279 /* Is it already here, but we can do the one-item-per-page
281 if (existing_entry && cache->items_per_page == 1)
283 /* Special case! ENTRY is the *only* entry on this page, so
284 * why not wipe it (so as not to leak the previous value).
286 struct cache_page *page = existing_entry->page;
288 /* This can't be the partial page: items_per_page == 1
289 * *never* has a partial page (except for in the temporary state
290 * that we're about to fake). */
291 SVN_ERR_ASSERT(page->next != NULL);
292 SVN_ERR_ASSERT(cache->partial_page == NULL);
294 erase_page(cache, page);
295 existing_entry = NULL;
298 /* Is it already here, and we just have to leak the old value? */
301 struct cache_page *page = existing_entry->page;
303 SVN_ERR(move_page_to_front(cache, page));
304 cache->data_size -= existing_entry->size;
307 SVN_ERR(cache->serialize_func(&existing_entry->value,
308 &existing_entry->size,
311 cache->data_size += existing_entry->size;
312 if (existing_entry->size == 0)
313 existing_entry->value = NULL;
317 existing_entry->value = NULL;
318 existing_entry->size = 0;
324 /* Do we not have a partial page to put it on, but we are allowed to
326 if (cache->partial_page == NULL && cache->unallocated_pages > 0)
328 cache->partial_page = apr_pcalloc(cache->cache_pool,
329 sizeof(*(cache->partial_page)));
330 cache->partial_page->page_pool = svn_pool_create(cache->cache_pool);
331 cache->partial_page_number_filled = 0;
332 (cache->unallocated_pages)--;
335 /* Do we really not have a partial page to put it on, even after the
336 * one-item-per-page optimization and checking the unallocated page
338 if (cache->partial_page == NULL)
340 struct cache_page *oldest_page = cache->sentinel->prev;
342 SVN_ERR_ASSERT(oldest_page != cache->sentinel);
344 /* Erase the page and put it in cache->partial_page. */
345 erase_page(cache, oldest_page);
348 SVN_ERR_ASSERT(cache->partial_page != NULL);
351 struct cache_page *page = cache->partial_page;
352 struct cache_entry *new_entry = apr_pcalloc(page->page_pool,
355 /* Copy the key and value into the page's pool. */
356 new_entry->key = duplicate_key(cache, key, page->page_pool);
359 SVN_ERR(cache->serialize_func(&new_entry->value,
363 cache->data_size += new_entry->size;
364 if (new_entry->size == 0)
365 new_entry->value = NULL;
369 new_entry->value = NULL;
373 /* Add the entry to the page's list. */
374 new_entry->page = page;
375 new_entry->next_entry = page->first_entry;
376 page->first_entry = new_entry;
378 /* Add the entry to the hash, using the *entry's* copy of the
380 apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry);
382 /* We've added something else to the partial page. */
383 (cache->partial_page_number_filled)++;
386 if (cache->partial_page_number_filled >= cache->items_per_page)
388 insert_page(cache, page);
389 cache->partial_page = NULL;
397 inprocess_cache_set(void *cache_void,
400 apr_pool_t *scratch_pool)
402 inprocess_cache_t *cache = cache_void;
405 SVN_MUTEX__WITH_LOCK(cache->mutex,
406 inprocess_cache_set_internal(cache,
414 /* Baton type for svn_cache__iter. */
415 struct cache_iter_baton {
416 svn_iter_apr_hash_cb_t user_cb;
420 /* Call the user's callback with the actual value, not the
421 cache_entry. Implements the svn_iter_apr_hash_cb_t
430 struct cache_iter_baton *b = baton;
431 struct cache_entry *entry = val;
432 return (b->user_cb)(b->user_baton, key, klen, entry->value, pool);
436 inprocess_cache_iter(svn_boolean_t *completed,
438 svn_iter_apr_hash_cb_t user_cb,
440 apr_pool_t *scratch_pool)
442 inprocess_cache_t *cache = cache_void;
443 struct cache_iter_baton b;
445 b.user_baton = user_baton;
447 SVN_MUTEX__WITH_LOCK(cache->mutex,
448 svn_iter_apr_hash(completed, cache->hash,
449 iter_cb, &b, scratch_pool));
455 inprocess_cache_get_partial_internal(void **value_p,
456 svn_boolean_t *found,
457 inprocess_cache_t *cache,
459 svn_cache__partial_getter_func_t func,
461 apr_pool_t *result_pool)
463 struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
470 SVN_ERR(move_page_to_front(cache, entry->page));
473 return func(value_p, entry->value, entry->size, baton, result_pool);
477 inprocess_cache_get_partial(void **value_p,
478 svn_boolean_t *found,
481 svn_cache__partial_getter_func_t func,
483 apr_pool_t *result_pool)
485 inprocess_cache_t *cache = cache_void;
488 SVN_MUTEX__WITH_LOCK(cache->mutex,
489 inprocess_cache_get_partial_internal(value_p,
503 inprocess_cache_set_partial_internal(inprocess_cache_t *cache,
505 svn_cache__partial_setter_func_t func,
507 apr_pool_t *scratch_pool)
509 struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
512 SVN_ERR(move_page_to_front(cache, entry->page));
514 cache->data_size -= entry->size;
515 SVN_ERR(func(&entry->value,
518 entry->page->page_pool));
519 cache->data_size += entry->size;
526 inprocess_cache_set_partial(void *cache_void,
528 svn_cache__partial_setter_func_t func,
530 apr_pool_t *scratch_pool)
532 inprocess_cache_t *cache = cache_void;
535 SVN_MUTEX__WITH_LOCK(cache->mutex,
536 inprocess_cache_set_partial_internal(cache,
546 inprocess_cache_is_cachable(void *cache_void, apr_size_t size)
548 /* Be relatively strict: per page we should not allocate more than
549 * we could spare as "unused" memory.
550 * But in most cases, nobody will ask anyway. And in no case, this
551 * will be used for checks _inside_ the cache code.
553 inprocess_cache_t *cache = cache_void;
554 return size < SVN_ALLOCATOR_RECOMMENDED_MAX_FREE / cache->items_per_page;
558 inprocess_cache_get_info_internal(inprocess_cache_t *cache,
559 svn_cache__info_t *info,
560 apr_pool_t *result_pool)
562 info->id = apr_pstrdup(result_pool, cache->id);
564 info->used_entries = apr_hash_count(cache->hash);
565 info->total_entries = cache->items_per_page * cache->total_pages;
567 info->used_size = cache->data_size;
568 info->data_size = cache->data_size;
569 info->total_size = cache->data_size
570 + cache->items_per_page * sizeof(struct cache_page)
571 + info->used_entries * sizeof(struct cache_entry);
578 inprocess_cache_get_info(void *cache_void,
579 svn_cache__info_t *info,
581 apr_pool_t *result_pool)
583 inprocess_cache_t *cache = cache_void;
585 SVN_MUTEX__WITH_LOCK(cache->mutex,
586 inprocess_cache_get_info_internal(cache,
593 static svn_cache__vtable_t inprocess_cache_vtable = {
596 inprocess_cache_iter,
597 inprocess_cache_is_cachable,
598 inprocess_cache_get_partial,
599 inprocess_cache_set_partial,
600 inprocess_cache_get_info
604 svn_cache__create_inprocess(svn_cache__t **cache_p,
605 svn_cache__serialize_func_t serialize,
606 svn_cache__deserialize_func_t deserialize,
609 apr_int64_t items_per_page,
610 svn_boolean_t thread_safe,
614 svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
615 inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache));
617 cache->id = apr_pstrdup(pool, id);
619 SVN_ERR_ASSERT(klen == APR_HASH_KEY_STRING || klen >= 1);
621 cache->hash = apr_hash_make(pool);
624 cache->serialize_func = serialize;
625 cache->deserialize_func = deserialize;
627 SVN_ERR_ASSERT(pages >= 1);
628 cache->total_pages = pages;
629 cache->unallocated_pages = pages;
630 SVN_ERR_ASSERT(items_per_page >= 1);
631 cache->items_per_page = items_per_page;
633 cache->sentinel = apr_pcalloc(pool, sizeof(*(cache->sentinel)));
634 cache->sentinel->prev = cache->sentinel;
635 cache->sentinel->next = cache->sentinel;
636 /* The sentinel doesn't need a pool. (We're happy to crash if we
637 * accidentally try to treat it like a real page.) */
639 SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
641 cache->cache_pool = pool;
643 wrapper->vtable = &inprocess_cache_vtable;
644 wrapper->cache_internal = cache;