1 /* caching.c : in-memory caching
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
29 #include "temp_serializer.h"
30 #include "../libsvn_fs/fs-loader.h"
32 #include "svn_config.h"
33 #include "svn_cache_config.h"
35 #include "svn_private_config.h"
37 #include "svn_pools.h"
39 #include "private/svn_debug.h"
40 #include "private/svn_subr_private.h"
42 /* Take the ORIGINAL string and replace all occurrences of ":" without
43 * limiting the key space. Allocate the result in POOL.
46 normalize_key_part(const char *original,
50 apr_size_t len = strlen(original);
51 svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool);
53 for (i = 0; i < len; ++i)
58 case ':': svn_stringbuf_appendbytes(normalized, "%_", 2);
60 case '%': svn_stringbuf_appendbytes(normalized, "%%", 2);
62 default : svn_stringbuf_appendbyte(normalized, c);
66 return normalized->data;
69 /* *CACHE_TXDELTAS, *CACHE_FULLTEXTS flags will be set according to
70 FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to use.
72 Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
73 for temporary allocations. */
75 read_config(const char **cache_namespace,
76 svn_boolean_t *cache_txdeltas,
77 svn_boolean_t *cache_fulltexts,
81 /* No cache namespace by default. I.e. all FS instances share the
82 * cached data. If you specify different namespaces, the data will
83 * share / compete for the same cache memory but keys will not match
84 * across namespaces and, thus, cached data will not be shared between
87 * Since the namespace will be concatenated with other elements to form
88 * the complete key prefix, we must make sure that the resulting string
89 * is unique and cannot be created by any other combination of elements.
92 = normalize_key_part(svn_hash__get_cstring(fs->config,
93 SVN_FS_CONFIG_FSFS_CACHE_NS,
97 /* don't cache text deltas by default.
98 * Once we reconstructed the fulltexts from the deltas,
99 * these deltas are rarely re-used. Therefore, only tools
100 * like svnadmin will activate this to speed up operations
104 = svn_hash__get_bool(fs->config,
105 SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
108 /* by default, cache fulltexts.
109 * Most SVN tools care about reconstructed file content.
110 * Thus, this is a reasonable default.
111 * SVN admin tools may set that to FALSE because fulltexts
112 * won't be re-used rendering the cache less effective
113 * by squeezing wanted data out.
116 = svn_hash__get_bool(fs->config,
117 SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
124 /* Implements svn_cache__error_handler_t
125 * This variant clears the error after logging it.
128 warn_and_continue_on_cache_errors(svn_error_t *err,
132 svn_fs_t *fs = baton;
133 (fs->warning)(fs->warning_baton, err);
134 svn_error_clear(err);
139 /* Implements svn_cache__error_handler_t
140 * This variant logs the error and passes it on to the callers.
143 warn_and_fail_on_cache_errors(svn_error_t *err,
147 svn_fs_t *fs = baton;
148 (fs->warning)(fs->warning_baton, err);
152 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
153 /* Baton to be used for the dump_cache_statistics() pool cleanup function, */
154 struct dump_cache_baton_t
156 /* the pool about to be cleaned up. Will be used for temp. allocations. */
159 /* the cache to dump the statistics for */
163 /* APR pool cleanup handler that will printf the statistics of the
164 cache referenced by the baton in BATON_VOID. */
166 dump_cache_statistics(void *baton_void)
168 struct dump_cache_baton_t *baton = baton_void;
170 apr_status_t result = APR_SUCCESS;
171 svn_cache__info_t info;
172 svn_string_t *text_stats;
173 apr_array_header_t *lines;
176 svn_error_t *err = svn_cache__get_info(baton->cache,
181 /* skip unused caches */
182 if (! err && (info.gets > 0 || info.sets > 0))
184 text_stats = svn_cache__format_info(&info, TRUE, baton->pool);
185 lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool);
187 for (i = 0; i < lines->nelts; ++i)
189 const char *line = APR_ARRAY_IDX(lines, i, const char *);
191 SVN_DBG(("%s\n", line));
196 /* process error returns */
199 result = err->apr_err;
200 svn_error_clear(err);
207 dump_global_cache_statistics(void *baton_void)
209 apr_pool_t *pool = baton_void;
211 svn_cache__info_t *info = svn_cache__membuffer_get_global_info(pool);
212 svn_string_t *text_stats = svn_cache__format_info(info, FALSE, pool);
213 apr_array_header_t *lines = svn_cstring_split(text_stats->data, "\n",
217 for (i = 0; i < lines->nelts; ++i)
219 const char *line = APR_ARRAY_IDX(lines, i, const char *);
221 SVN_DBG(("%s\n", line));
228 #endif /* SVN_DEBUG_CACHE_DUMP_STATS */
230 /* This function sets / registers the required callbacks for a given
231 * not transaction-specific CACHE object in FS, if CACHE is not NULL.
233 * All these svn_cache__t instances shall be handled uniformly. Unless
234 * ERROR_HANDLER is NULL, register it for the given CACHE in FS.
237 init_callbacks(svn_cache__t *cache,
239 svn_cache__error_handler_t error_handler,
244 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
246 /* schedule printing the access statistics upon pool cleanup,
247 * i.e. end of FSFS session.
249 struct dump_cache_baton_t *baton;
251 baton = apr_palloc(pool, sizeof(*baton));
253 baton->cache = cache;
255 apr_pool_cleanup_register(pool,
257 dump_cache_statistics,
258 apr_pool_cleanup_null);
262 SVN_ERR(svn_cache__set_error_handler(cache,
272 /* Sets *CACHE_P to cache instance based on provided options.
273 * Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if
274 * MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
275 * MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
276 * otherwise. Use the given PRIORITY class for the new cache. If it
277 * is 0, then use the default priority class.
279 * Unless NO_HANDLER is true, register an error handler that reports errors
280 * as warnings to the FS warning callback.
282 * Cache is allocated in RESULT_POOL, temporaries in SCRATCH_POOL.
285 create_cache(svn_cache__t **cache_p,
286 svn_memcache_t *memcache,
287 svn_membuffer_t *membuffer,
289 apr_int64_t items_per_page,
290 svn_cache__serialize_func_t serializer,
291 svn_cache__deserialize_func_t deserializer,
294 apr_uint32_t priority,
296 svn_boolean_t no_handler,
297 apr_pool_t *result_pool,
298 apr_pool_t *scratch_pool)
300 svn_cache__error_handler_t error_handler = no_handler
302 : warn_and_fail_on_cache_errors;
304 priority = SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY;
308 SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
309 serializer, deserializer, klen,
310 prefix, result_pool));
311 error_handler = no_handler
313 : warn_and_continue_on_cache_errors;
317 SVN_ERR(svn_cache__create_membuffer_cache(
318 cache_p, membuffer, serializer, deserializer,
319 klen, prefix, priority, FALSE, result_pool, scratch_pool));
323 SVN_ERR(svn_cache__create_inprocess(
324 cache_p, serializer, deserializer, klen, pages,
325 items_per_page, FALSE, prefix, result_pool));
332 SVN_ERR(init_callbacks(*cache_p, fs, error_handler, result_pool));
338 svn_fs_fs__initialize_caches(svn_fs_t *fs,
341 fs_fs_data_t *ffd = fs->fsap_data;
342 const char *prefix = apr_pstrcat(pool,
344 "/", normalize_key_part(fs->path, pool),
347 svn_membuffer_t *membuffer;
348 svn_boolean_t no_handler = ffd->fail_stop;
349 svn_boolean_t cache_txdeltas;
350 svn_boolean_t cache_fulltexts;
351 const char *cache_namespace;
353 /* Evaluating the cache configuration. */
354 SVN_ERR(read_config(&cache_namespace,
360 prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, SVN_VA_NULL);
362 membuffer = svn_cache__get_global_membuffer_cache();
364 /* General rules for assigning cache priorities:
366 * - Data that can be reconstructed from other elements has low prio
367 * (e.g. fulltexts, directories etc.)
368 * - Index data required to find any of the other data has high prio
369 * (e.g. noderevs, L2P and P2L index pages)
370 * - everthing else should use default prio
373 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
375 /* schedule printing the global access statistics upon pool cleanup,
376 * i.e. when the repo instance gets closed / cleaned up.
379 apr_pool_cleanup_register(fs->pool,
381 dump_global_cache_statistics,
382 apr_pool_cleanup_null);
385 /* Make the cache for revision roots. For the vast majority of
386 * commands, this is only going to contain a few entries (svnadmin
387 * dump/verify is an exception here), so to reduce overhead let's
388 * try to keep it to just one page. I estimate each entry has about
389 * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t +
390 * id_private_t + 3 strings for value, and the cache_entry); the
391 * default pool size is 8192, so about a hundred should fit
393 SVN_ERR(create_cache(&(ffd->rev_root_id_cache),
397 svn_fs_fs__serialize_id,
398 svn_fs_fs__deserialize_id,
399 sizeof(svn_revnum_t),
400 apr_pstrcat(pool, prefix, "RRI", SVN_VA_NULL),
406 /* Rough estimate: revision DAG nodes have size around 320 bytes, so
407 * let's put 16 on a page. */
408 SVN_ERR(create_cache(&(ffd->rev_node_cache),
412 svn_fs_fs__dag_serialize,
413 svn_fs_fs__dag_deserialize,
415 apr_pstrcat(pool, prefix, "DAG", SVN_VA_NULL),
416 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
421 /* 1st level DAG node cache */
422 ffd->dag_node_cache = svn_fs_fs__create_dag_cache(fs->pool);
424 /* Very rough estimate: 1K per directory. */
425 SVN_ERR(create_cache(&(ffd->dir_cache),
429 svn_fs_fs__serialize_dir_entries,
430 svn_fs_fs__deserialize_dir_entries,
431 sizeof(pair_cache_key_t),
432 apr_pstrcat(pool, prefix, "DIR", SVN_VA_NULL),
433 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
438 /* Only 16 bytes per entry (a revision number + the corresponding offset).
439 Since we want ~8k pages, that means 512 entries per page. */
440 SVN_ERR(create_cache(&(ffd->packed_offset_cache),
444 svn_fs_fs__serialize_manifest,
445 svn_fs_fs__deserialize_manifest,
446 sizeof(svn_revnum_t),
447 apr_pstrcat(pool, prefix, "PACK-MANIFEST",
449 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
454 /* initialize node revision cache, if caching has been enabled */
455 SVN_ERR(create_cache(&(ffd->node_revision_cache),
458 32, 32, /* ~200 byte / entry; 1k entries total */
459 svn_fs_fs__serialize_node_revision,
460 svn_fs_fs__deserialize_node_revision,
461 sizeof(pair_cache_key_t),
462 apr_pstrcat(pool, prefix, "NODEREVS", SVN_VA_NULL),
463 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
468 /* initialize representation header cache, if caching has been enabled */
469 SVN_ERR(create_cache(&(ffd->rep_header_cache),
472 1, 1000, /* ~8 bytes / entry; 1k entries total */
473 svn_fs_fs__serialize_rep_header,
474 svn_fs_fs__deserialize_rep_header,
475 sizeof(pair_cache_key_t),
476 apr_pstrcat(pool, prefix, "REPHEADER", SVN_VA_NULL),
477 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
482 /* initialize node change list cache, if caching has been enabled */
483 SVN_ERR(create_cache(&(ffd->changes_cache),
486 1, 8, /* 1k / entry; 8 entries total, rarely used */
487 svn_fs_fs__serialize_changes,
488 svn_fs_fs__deserialize_changes,
489 sizeof(svn_revnum_t),
490 apr_pstrcat(pool, prefix, "CHANGES", SVN_VA_NULL),
496 /* if enabled, cache fulltext and other derived information */
499 SVN_ERR(create_cache(&(ffd->fulltext_cache),
502 0, 0, /* Do not use inprocess cache */
503 /* Values are svn_stringbuf_t */
505 sizeof(pair_cache_key_t),
506 apr_pstrcat(pool, prefix, "TEXT", SVN_VA_NULL),
507 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
512 SVN_ERR(create_cache(&(ffd->properties_cache),
515 0, 0, /* Do not use inprocess cache */
516 svn_fs_fs__serialize_properties,
517 svn_fs_fs__deserialize_properties,
518 sizeof(pair_cache_key_t),
519 apr_pstrcat(pool, prefix, "PROP",
521 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
526 SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
529 0, 0, /* Do not use inprocess cache */
530 svn_fs_fs__serialize_mergeinfo,
531 svn_fs_fs__deserialize_mergeinfo,
533 apr_pstrcat(pool, prefix, "MERGEINFO",
540 SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
543 0, 0, /* Do not use inprocess cache */
544 /* Values are svn_stringbuf_t */
547 apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
556 ffd->fulltext_cache = NULL;
557 ffd->properties_cache = NULL;
558 ffd->mergeinfo_cache = NULL;
559 ffd->mergeinfo_existence_cache = NULL;
562 /* if enabled, cache text deltas and their combinations */
565 SVN_ERR(create_cache(&(ffd->raw_window_cache),
568 0, 0, /* Do not use inprocess cache */
569 svn_fs_fs__serialize_raw_window,
570 svn_fs_fs__deserialize_raw_window,
571 sizeof(window_cache_key_t),
572 apr_pstrcat(pool, prefix, "RAW_WINDOW",
574 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
579 SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
582 0, 0, /* Do not use inprocess cache */
583 svn_fs_fs__serialize_txdelta_window,
584 svn_fs_fs__deserialize_txdelta_window,
585 sizeof(window_cache_key_t),
586 apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
588 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
593 SVN_ERR(create_cache(&(ffd->combined_window_cache),
596 0, 0, /* Do not use inprocess cache */
597 /* Values are svn_stringbuf_t */
599 sizeof(window_cache_key_t),
600 apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
602 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
609 ffd->txdelta_window_cache = NULL;
610 ffd->combined_window_cache = NULL;
613 SVN_ERR(create_cache(&(ffd->l2p_header_cache),
616 64, 16, /* entry size varies but we must cover
617 a reasonable number of revisions (1k) */
618 svn_fs_fs__serialize_l2p_header,
619 svn_fs_fs__deserialize_l2p_header,
620 sizeof(pair_cache_key_t),
621 apr_pstrcat(pool, prefix, "L2P_HEADER",
623 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
627 SVN_ERR(create_cache(&(ffd->l2p_page_cache),
630 64, 16, /* entry size varies but we must cover
631 a reasonable number of revisions (1k) */
632 svn_fs_fs__serialize_l2p_page,
633 svn_fs_fs__deserialize_l2p_page,
634 sizeof(svn_fs_fs__page_cache_key_t),
635 apr_pstrcat(pool, prefix, "L2P_PAGE",
637 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
641 SVN_ERR(create_cache(&(ffd->p2l_header_cache),
644 4, 1, /* Large entries. Rarely used. */
645 svn_fs_fs__serialize_p2l_header,
646 svn_fs_fs__deserialize_p2l_header,
647 sizeof(pair_cache_key_t),
648 apr_pstrcat(pool, prefix, "P2L_HEADER",
650 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
654 SVN_ERR(create_cache(&(ffd->p2l_page_cache),
657 4, 16, /* Variably sized entries. Rarely used. */
658 svn_fs_fs__serialize_p2l_page,
659 svn_fs_fs__deserialize_p2l_page,
660 sizeof(svn_fs_fs__page_cache_key_t),
661 apr_pstrcat(pool, prefix, "P2L_PAGE",
663 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
671 /* Baton to be used for the remove_txn_cache() pool cleanup function, */
672 struct txn_cleanup_baton_t
674 /* the cache to reset */
675 svn_cache__t *txn_cache;
677 /* the position where to reset it */
678 svn_cache__t **to_reset;
680 /* pool that TXN_CACHE was allocated in */
681 apr_pool_t *txn_pool;
683 /* pool that the FS containing the TO_RESET pointer was allocator */
687 /* Forward declaration. */
689 remove_txn_cache_fs(void *baton_void);
691 /* APR pool cleanup handler that will reset the cache pointer given in
692 BATON_VOID when the TXN_POOL gets cleaned up. */
694 remove_txn_cache_txn(void *baton_void)
696 struct txn_cleanup_baton_t *baton = baton_void;
698 /* be careful not to hurt performance by resetting newer txn's caches. */
699 if (*baton->to_reset == baton->txn_cache)
701 /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
702 *baton->to_reset = NULL;
705 /* It's cleaned up now. Prevent double cleanup. */
706 apr_pool_cleanup_kill(baton->fs_pool,
708 remove_txn_cache_fs);
713 /* APR pool cleanup handler that will reset the cache pointer given in
714 BATON_VOID when the FS_POOL gets cleaned up. */
716 remove_txn_cache_fs(void *baton_void)
718 struct txn_cleanup_baton_t *baton = baton_void;
720 /* be careful not to hurt performance by resetting newer txn's caches. */
721 if (*baton->to_reset == baton->txn_cache)
723 /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
724 *baton->to_reset = NULL;
727 /* It's cleaned up now. Prevent double cleanup. */
728 apr_pool_cleanup_kill(baton->txn_pool,
730 remove_txn_cache_txn);
735 /* This function sets / registers the required callbacks for a given
736 * transaction-specific *CACHE object in FS, if CACHE is not NULL and
737 * a no-op otherwise. In particular, it will ensure that *CACHE gets
738 * reset to NULL upon POOL or FS->POOL destruction latest.
741 init_txn_callbacks(svn_fs_t *fs,
742 svn_cache__t **cache,
747 struct txn_cleanup_baton_t *baton;
749 baton = apr_palloc(pool, sizeof(*baton));
750 baton->txn_cache = *cache;
751 baton->to_reset = cache;
752 baton->txn_pool = pool;
753 baton->fs_pool = fs->pool;
755 /* If any of these pools gets cleaned, we must reset the cache.
756 * We don't know which one will get cleaned up first, so register
757 * cleanup actions for both and during the cleanup action, unregister
758 * the respective other action. */
759 apr_pool_cleanup_register(pool,
761 remove_txn_cache_txn,
762 apr_pool_cleanup_null);
763 apr_pool_cleanup_register(fs->pool,
766 apr_pool_cleanup_null);
771 svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
775 fs_fs_data_t *ffd = fs->fsap_data;
777 /* Transaction content needs to be carefully prefixed to virtually
778 eliminate any chance for conflicts. The (repo, txn_id) pair
779 should be unique but if a transaction fails, it might be possible
780 to start a new transaction later that receives the same id.
781 Therefore, throw in a uuid as well - just to be sure. */
782 const char *prefix = apr_pstrcat(pool,
786 ":", svn_uuid_generate(pool), ":",
789 /* We don't support caching for concurrent transactions in the SAME
790 * FSFS session. Maybe, you forgot to clean POOL. */
791 if (ffd->txn_dir_cache != NULL || ffd->concurrent_transactions)
793 ffd->txn_dir_cache = NULL;
794 ffd->concurrent_transactions = TRUE;
799 /* create a txn-local directory cache */
800 SVN_ERR(create_cache(&ffd->txn_dir_cache,
802 svn_cache__get_global_membuffer_cache(),
804 svn_fs_fs__serialize_dir_entries,
805 svn_fs_fs__deserialize_dir_entries,
807 apr_pstrcat(pool, prefix, "TXNDIR",
814 /* reset the transaction-specific cache if the pool gets cleaned up. */
815 init_txn_callbacks(fs, &(ffd->txn_dir_cache), pool);
821 svn_fs_fs__reset_txn_caches(svn_fs_t *fs)
823 /* we can always just reset the caches. This may degrade performance but
824 * can never cause in incorrect behavior. */
826 fs_fs_data_t *ffd = fs->fsap_data;
827 ffd->txn_dir_cache = NULL;