]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_subr/cache-inprocess.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_subr / cache-inprocess.c
1 /*
2  * cache-inprocess.c: in-memory caching for Subversion
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24 #include <assert.h>
25
26 #include <apr_thread_mutex.h>
27
28 #include "svn_pools.h"
29
30 #include "svn_private_config.h"
31
32 #include "cache.h"
33 #include "private/svn_mutex.h"
34
35 /* The (internal) cache object. */
36 typedef struct inprocess_cache_t {
37   /* A user-defined identifier for this cache instance. */
38   const char *id;
39
40   /* HASH maps a key (of size KLEN) to a struct cache_entry. */
41   apr_hash_t *hash;
42   apr_ssize_t klen;
43
44   /* Used to copy values into the cache. */
45   svn_cache__serialize_func_t serialize_func;
46
47   /* Used to copy values out of the cache. */
48   svn_cache__deserialize_func_t deserialize_func;
49
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;
57
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;
64
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;
71
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.
75    */
76   apr_pool_t *cache_pool;
77
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.
81    */
82   apr_size_t data_size;
83
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
86    * thread-safe. */
87   svn_mutex__t *mutex;
88 } inprocess_cache_t;
89
90 /* A cache page; all items on the page are allocated from the same
91  * pool. */
92 struct cache_page {
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;
97
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.)
103    */
104   apr_pool_t *page_pool;
105
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;
109 };
110
111 /* An cache entry. */
112 struct cache_entry {
113   const void *key;
114
115   /* serialized value */
116   void *value;
117
118   /* length of the serialized value in bytes */
119   apr_size_t size;
120
121   /* The page it's on (needed so that the LRU list can be
122    * maintained). */
123   struct cache_page *page;
124
125   /* Next entry on the page. */
126   struct cache_entry *next_entry;
127 };
128
129
130 /* Removes PAGE from the doubly-linked list it is in (leaving its PREV
131  * and NEXT fields undefined). */
132 static void
133 remove_page_from_list(struct cache_page *page)
134 {
135   page->prev->next = page->next;
136   page->next->prev = page->prev;
137 }
138
139 /* Inserts PAGE after CACHE's sentinel. */
140 static void
141 insert_page(inprocess_cache_t *cache,
142             struct cache_page *page)
143 {
144   struct cache_page *pred = cache->sentinel;
145
146   page->prev = pred;
147   page->next = pred->next;
148   page->prev->next = page;
149   page->next->prev = page;
150 }
151
152 /* If PAGE is in the circularly linked list (eg, its NEXT isn't NULL),
153  * move it to the front of the list. */
154 static svn_error_t *
155 move_page_to_front(inprocess_cache_t *cache,
156                    struct cache_page *page)
157 {
158   /* This function is called whilst CACHE is locked. */
159
160   SVN_ERR_ASSERT(page != cache->sentinel);
161
162   if (! page->next)
163     return SVN_NO_ERROR;
164
165   remove_page_from_list(page);
166   insert_page(cache, page);
167
168   return SVN_NO_ERROR;
169 }
170
171 /* Return a copy of KEY inside POOL, using CACHE->KLEN to figure out
172  * how. */
173 static const void *
174 duplicate_key(inprocess_cache_t *cache,
175               const void *key,
176               apr_pool_t *pool)
177 {
178   if (cache->klen == APR_HASH_KEY_STRING)
179     return apr_pstrdup(pool, key);
180   else
181     return apr_pmemdup(pool, key, cache->klen);
182 }
183
184 static svn_error_t *
185 inprocess_cache_get_internal(char **buffer,
186                              apr_size_t *size,
187                              inprocess_cache_t *cache,
188                              const void *key,
189                              apr_pool_t *result_pool)
190 {
191   struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
192
193   *buffer = NULL;
194   if (entry)
195     {
196       SVN_ERR(move_page_to_front(cache, entry->page));
197
198       /* duplicate the buffer entry */
199       *buffer = apr_palloc(result_pool, entry->size);
200       memcpy(*buffer, entry->value, entry->size);
201
202       *size = entry->size;
203     }
204
205   return SVN_NO_ERROR;
206 }
207
208 static svn_error_t *
209 inprocess_cache_get(void **value_p,
210                     svn_boolean_t *found,
211                     void *cache_void,
212                     const void *key,
213                     apr_pool_t *result_pool)
214 {
215   inprocess_cache_t *cache = cache_void;
216   char* buffer = NULL;
217   apr_size_t size;
218
219   if (key)
220     SVN_MUTEX__WITH_LOCK(cache->mutex,
221                          inprocess_cache_get_internal(&buffer,
222                                                       &size,
223                                                       cache,
224                                                       key,
225                                                       result_pool));
226
227   /* deserialize the buffer content. Usually, this will directly
228      modify the buffer content directly.
229    */
230   *value_p = NULL;
231   *found = buffer != NULL;
232   return buffer && size
233     ? cache->deserialize_func(value_p, buffer, size, result_pool)
234     : SVN_NO_ERROR;
235 }
236
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
241  * in the list. */
242 static void
243 erase_page(inprocess_cache_t *cache,
244            struct cache_page *page)
245 {
246   struct cache_entry *e;
247
248   remove_page_from_list(page);
249
250   for (e = page->first_entry;
251        e;
252        e = e->next_entry)
253     {
254       cache->data_size -= e->size;
255       apr_hash_set(cache->hash, e->key, cache->klen, NULL);
256     }
257
258   svn_pool_clear(page->page_pool);
259
260   page->first_entry = NULL;
261   page->prev = NULL;
262   page->next = NULL;
263
264   cache->partial_page = page;
265   cache->partial_page_number_filled = 0;
266 }
267
268
269 static svn_error_t *
270 inprocess_cache_set_internal(inprocess_cache_t *cache,
271                              const void *key,
272                              void *value,
273                              apr_pool_t *scratch_pool)
274 {
275   struct cache_entry *existing_entry;
276
277   existing_entry = apr_hash_get(cache->hash, key, cache->klen);
278
279   /* Is it already here, but we can do the one-item-per-page
280    * optimization? */
281   if (existing_entry && cache->items_per_page == 1)
282     {
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).
285        */
286       struct cache_page *page = existing_entry->page;
287
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);
293
294       erase_page(cache, page);
295       existing_entry = NULL;
296     }
297
298   /* Is it already here, and we just have to leak the old value? */
299   if (existing_entry)
300     {
301       struct cache_page *page = existing_entry->page;
302
303       SVN_ERR(move_page_to_front(cache, page));
304       cache->data_size -= existing_entry->size;
305       if (value)
306         {
307           SVN_ERR(cache->serialize_func(&existing_entry->value,
308                                         &existing_entry->size,
309                                         value,
310                                         page->page_pool));
311           cache->data_size += existing_entry->size;
312           if (existing_entry->size == 0)
313             existing_entry->value = NULL;
314         }
315       else
316         {
317           existing_entry->value = NULL;
318           existing_entry->size = 0;
319         }
320
321       return SVN_NO_ERROR;
322     }
323
324   /* Do we not have a partial page to put it on, but we are allowed to
325    * allocate more? */
326   if (cache->partial_page == NULL && cache->unallocated_pages > 0)
327     {
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)--;
333     }
334
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
337    * count? */
338   if (cache->partial_page == NULL)
339     {
340       struct cache_page *oldest_page = cache->sentinel->prev;
341
342       SVN_ERR_ASSERT(oldest_page != cache->sentinel);
343
344       /* Erase the page and put it in cache->partial_page. */
345       erase_page(cache, oldest_page);
346     }
347
348   SVN_ERR_ASSERT(cache->partial_page != NULL);
349
350   {
351     struct cache_page *page = cache->partial_page;
352     struct cache_entry *new_entry = apr_pcalloc(page->page_pool,
353                                                 sizeof(*new_entry));
354
355     /* Copy the key and value into the page's pool.  */
356     new_entry->key = duplicate_key(cache, key, page->page_pool);
357     if (value)
358       {
359         SVN_ERR(cache->serialize_func(&new_entry->value,
360                                       &new_entry->size,
361                                       value,
362                                       page->page_pool));
363         cache->data_size += new_entry->size;
364         if (new_entry->size == 0)
365           new_entry->value = NULL;
366       }
367     else
368       {
369         new_entry->value = NULL;
370         new_entry->size = 0;
371       }
372
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;
377
378     /* Add the entry to the hash, using the *entry's* copy of the
379      * key. */
380     apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry);
381
382     /* We've added something else to the partial page. */
383     (cache->partial_page_number_filled)++;
384
385     /* Is it full? */
386     if (cache->partial_page_number_filled >= cache->items_per_page)
387       {
388         insert_page(cache, page);
389         cache->partial_page = NULL;
390       }
391   }
392
393   return SVN_NO_ERROR;
394 }
395
396 static svn_error_t *
397 inprocess_cache_set(void *cache_void,
398                     const void *key,
399                     void *value,
400                     apr_pool_t *scratch_pool)
401 {
402   inprocess_cache_t *cache = cache_void;
403
404   if (key)
405     SVN_MUTEX__WITH_LOCK(cache->mutex,
406                          inprocess_cache_set_internal(cache,
407                                                       key,
408                                                       value,
409                                                       scratch_pool));
410
411   return SVN_NO_ERROR;
412 }
413
414 /* Baton type for svn_cache__iter. */
415 struct cache_iter_baton {
416   svn_iter_apr_hash_cb_t user_cb;
417   void *user_baton;
418 };
419
420 /* Call the user's callback with the actual value, not the
421    cache_entry.  Implements the svn_iter_apr_hash_cb_t
422    prototype. */
423 static svn_error_t *
424 iter_cb(void *baton,
425         const void *key,
426         apr_ssize_t klen,
427         void *val,
428         apr_pool_t *pool)
429 {
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);
433 }
434
435 static svn_error_t *
436 inprocess_cache_iter(svn_boolean_t *completed,
437                      void *cache_void,
438                      svn_iter_apr_hash_cb_t user_cb,
439                      void *user_baton,
440                      apr_pool_t *scratch_pool)
441 {
442   inprocess_cache_t *cache = cache_void;
443   struct cache_iter_baton b;
444   b.user_cb = user_cb;
445   b.user_baton = user_baton;
446
447   SVN_MUTEX__WITH_LOCK(cache->mutex,
448                        svn_iter_apr_hash(completed, cache->hash,
449                                          iter_cb, &b, scratch_pool));
450
451   return SVN_NO_ERROR;
452 }
453
454 static svn_error_t *
455 inprocess_cache_get_partial_internal(void **value_p,
456                                      svn_boolean_t *found,
457                                      inprocess_cache_t *cache,
458                                      const void *key,
459                                      svn_cache__partial_getter_func_t func,
460                                      void *baton,
461                                      apr_pool_t *result_pool)
462 {
463   struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
464   if (! entry)
465     {
466       *found = FALSE;
467       return SVN_NO_ERROR;
468     }
469
470   SVN_ERR(move_page_to_front(cache, entry->page));
471
472   *found = TRUE;
473   return func(value_p, entry->value, entry->size, baton, result_pool);
474 }
475
476 static svn_error_t *
477 inprocess_cache_get_partial(void **value_p,
478                             svn_boolean_t *found,
479                             void *cache_void,
480                             const void *key,
481                             svn_cache__partial_getter_func_t func,
482                             void *baton,
483                             apr_pool_t *result_pool)
484 {
485   inprocess_cache_t *cache = cache_void;
486
487   if (key)
488     SVN_MUTEX__WITH_LOCK(cache->mutex,
489                          inprocess_cache_get_partial_internal(value_p,
490                                                               found,
491                                                               cache,
492                                                               key,
493                                                               func,
494                                                               baton,
495                                                               result_pool));
496   else
497     *found = FALSE;
498
499   return SVN_NO_ERROR;
500 }
501
502 static svn_error_t *
503 inprocess_cache_set_partial_internal(inprocess_cache_t *cache,
504                                      const void *key,
505                                      svn_cache__partial_setter_func_t func,
506                                      void *baton,
507                                      apr_pool_t *scratch_pool)
508 {
509   struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
510   if (entry)
511     {
512       SVN_ERR(move_page_to_front(cache, entry->page));
513
514       cache->data_size -= entry->size;
515       SVN_ERR(func(&entry->value,
516                    &entry->size,
517                    baton,
518                    entry->page->page_pool));
519       cache->data_size += entry->size;
520     }
521
522   return SVN_NO_ERROR;
523 }
524
525 static svn_error_t *
526 inprocess_cache_set_partial(void *cache_void,
527                             const void *key,
528                             svn_cache__partial_setter_func_t func,
529                             void *baton,
530                             apr_pool_t *scratch_pool)
531 {
532   inprocess_cache_t *cache = cache_void;
533
534   if (key)
535     SVN_MUTEX__WITH_LOCK(cache->mutex,
536                          inprocess_cache_set_partial_internal(cache,
537                                                               key,
538                                                               func,
539                                                               baton,
540                                                               scratch_pool));
541
542   return SVN_NO_ERROR;
543 }
544
545 static svn_boolean_t
546 inprocess_cache_is_cachable(void *cache_void, apr_size_t size)
547 {
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.
552    */
553   inprocess_cache_t *cache = cache_void;
554   return size < SVN_ALLOCATOR_RECOMMENDED_MAX_FREE / cache->items_per_page;
555 }
556
557 static svn_error_t *
558 inprocess_cache_get_info_internal(inprocess_cache_t *cache,
559                                   svn_cache__info_t *info,
560                                   apr_pool_t *result_pool)
561 {
562   info->id = apr_pstrdup(result_pool, cache->id);
563
564   info->used_entries = apr_hash_count(cache->hash);
565   info->total_entries = cache->items_per_page * cache->total_pages;
566
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);
572
573   return SVN_NO_ERROR;
574 }
575
576
577 static svn_error_t *
578 inprocess_cache_get_info(void *cache_void,
579                          svn_cache__info_t *info,
580                          svn_boolean_t reset,
581                          apr_pool_t *result_pool)
582 {
583   inprocess_cache_t *cache = cache_void;
584
585   SVN_MUTEX__WITH_LOCK(cache->mutex,
586                        inprocess_cache_get_info_internal(cache,
587                                                          info,
588                                                          result_pool));
589
590   return SVN_NO_ERROR;
591 }
592
593 static svn_cache__vtable_t inprocess_cache_vtable = {
594   inprocess_cache_get,
595   inprocess_cache_set,
596   inprocess_cache_iter,
597   inprocess_cache_is_cachable,
598   inprocess_cache_get_partial,
599   inprocess_cache_set_partial,
600   inprocess_cache_get_info
601 };
602
603 svn_error_t *
604 svn_cache__create_inprocess(svn_cache__t **cache_p,
605                             svn_cache__serialize_func_t serialize,
606                             svn_cache__deserialize_func_t deserialize,
607                             apr_ssize_t klen,
608                             apr_int64_t pages,
609                             apr_int64_t items_per_page,
610                             svn_boolean_t thread_safe,
611                             const char *id,
612                             apr_pool_t *pool)
613 {
614   svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
615   inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache));
616
617   cache->id = apr_pstrdup(pool, id);
618
619   SVN_ERR_ASSERT(klen == APR_HASH_KEY_STRING || klen >= 1);
620
621   cache->hash = apr_hash_make(pool);
622   cache->klen = klen;
623
624   cache->serialize_func = serialize;
625   cache->deserialize_func = deserialize;
626
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;
632
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.) */
638
639   SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
640
641   cache->cache_pool = pool;
642
643   wrapper->vtable = &inprocess_cache_vtable;
644   wrapper->cache_internal = cache;
645
646   *cache_p = wrapper;
647   return SVN_NO_ERROR;
648 }