blob: 0c8d2fc8ca3f092b3779411e521d823c4667cae0 [file] [log] [blame]
/* caching.c : in-memory caching
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include "fs.h"
#include "fs_fs.h"
#include "id.h"
#include "dag.h"
#include "tree.h"
#include "index.h"
#include "temp_serializer.h"
#include "../libsvn_fs/fs-loader.h"
#include "svn_config.h"
#include "svn_cache_config.h"
#include "svn_private_config.h"
#include "svn_hash.h"
#include "svn_pools.h"
#include "private/svn_debug.h"
#include "private/svn_subr_private.h"
/* Take the ORIGINAL string and replace all occurrences of ":" without
* limiting the key space. Allocate the result in POOL.
*/
static const char *
normalize_key_part(const char *original,
apr_pool_t *pool)
{
apr_size_t i;
apr_size_t len = strlen(original);
svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool);
for (i = 0; i < len; ++i)
{
char c = original[i];
switch (c)
{
case ':': svn_stringbuf_appendbytes(normalized, "%_", 2);
break;
case '%': svn_stringbuf_appendbytes(normalized, "%%", 2);
break;
default : svn_stringbuf_appendbyte(normalized, c);
}
}
return normalized->data;
}
/* *CACHE_TXDELTAS, *CACHE_FULLTEXTS, *CACHE_NODEPROPS flags will be set
according to FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to
use.
Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
for temporary allocations. */
static svn_error_t *
read_config(const char **cache_namespace,
svn_boolean_t *cache_txdeltas,
svn_boolean_t *cache_fulltexts,
svn_boolean_t *cache_nodeprops,
svn_fs_t *fs,
apr_pool_t *pool)
{
/* No cache namespace by default. I.e. all FS instances share the
* cached data. If you specify different namespaces, the data will
* share / compete for the same cache memory but keys will not match
* across namespaces and, thus, cached data will not be shared between
* namespaces.
*
* Since the namespace will be concatenated with other elements to form
* the complete key prefix, we must make sure that the resulting string
* is unique and cannot be created by any other combination of elements.
*/
*cache_namespace
= normalize_key_part(svn_hash__get_cstring(fs->config,
SVN_FS_CONFIG_FSFS_CACHE_NS,
""),
pool);
/* don't cache text deltas by default.
* Once we reconstructed the fulltexts from the deltas,
* these deltas are rarely re-used. Therefore, only tools
* like svnadmin will activate this to speed up operations
* dump and verify.
*/
*cache_txdeltas
= svn_hash__get_bool(fs->config,
SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
TRUE);
/* by default, cache fulltexts.
* Most SVN tools care about reconstructed file content.
* Thus, this is a reasonable default.
* SVN admin tools may set that to FALSE because fulltexts
* won't be re-used rendering the cache less effective
* by squeezing wanted data out.
*/
*cache_fulltexts
= svn_hash__get_bool(fs->config,
SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
TRUE);
/* by default, cache nodeprops: this will match pre-1.10
* behavior where node properties caching was controlled
* by SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS configuration option.
*/
*cache_nodeprops
= svn_hash__get_bool(fs->config,
SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS,
TRUE);
return SVN_NO_ERROR;
}
/* Implements svn_cache__error_handler_t
* This variant clears the error after logging it.
*/
static svn_error_t *
warn_and_continue_on_cache_errors(svn_error_t *err,
void *baton,
apr_pool_t *pool)
{
svn_fs_t *fs = baton;
(fs->warning)(fs->warning_baton, err);
svn_error_clear(err);
return SVN_NO_ERROR;
}
/* Implements svn_cache__error_handler_t
* This variant logs the error and passes it on to the callers.
*/
static svn_error_t *
warn_and_fail_on_cache_errors(svn_error_t *err,
void *baton,
apr_pool_t *pool)
{
svn_fs_t *fs = baton;
(fs->warning)(fs->warning_baton, err);
return err;
}
#ifdef SVN_DEBUG_CACHE_DUMP_STATS
/* Baton to be used for the dump_cache_statistics() pool cleanup function, */
struct dump_cache_baton_t
{
/* the pool about to be cleaned up. Will be used for temp. allocations. */
apr_pool_t *pool;
/* the cache to dump the statistics for */
svn_cache__t *cache;
};
/* APR pool cleanup handler that will printf the statistics of the
cache referenced by the baton in BATON_VOID. */
static apr_status_t
dump_cache_statistics(void *baton_void)
{
struct dump_cache_baton_t *baton = baton_void;
apr_status_t result = APR_SUCCESS;
svn_cache__info_t info;
svn_string_t *text_stats;
apr_array_header_t *lines;
int i;
svn_error_t *err = svn_cache__get_info(baton->cache,
&info,
TRUE,
baton->pool);
/* skip unused caches */
if (! err && (info.gets > 0 || info.sets > 0))
{
text_stats = svn_cache__format_info(&info, TRUE, baton->pool);
lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool);
for (i = 0; i < lines->nelts; ++i)
{
const char *line = APR_ARRAY_IDX(lines, i, const char *);
#ifdef SVN_DEBUG
SVN_DBG(("%s\n", line));
#endif
}
}
/* process error returns */
if (err)
{
result = err->apr_err;
svn_error_clear(err);
}
return result;
}
static apr_status_t
dump_global_cache_statistics(void *baton_void)
{
apr_pool_t *pool = baton_void;
svn_cache__info_t *info = svn_cache__membuffer_get_global_info(pool);
svn_string_t *text_stats = svn_cache__format_info(info, FALSE, pool);
apr_array_header_t *lines = svn_cstring_split(text_stats->data, "\n",
FALSE, pool);
int i;
for (i = 0; i < lines->nelts; ++i)
{
const char *line = APR_ARRAY_IDX(lines, i, const char *);
#ifdef SVN_DEBUG
SVN_DBG(("%s\n", line));
#endif
}
return APR_SUCCESS;
}
#endif /* SVN_DEBUG_CACHE_DUMP_STATS */
/* This function sets / registers the required callbacks for a given
* not transaction-specific CACHE object in FS, if CACHE is not NULL.
*
* All these svn_cache__t instances shall be handled uniformly. Unless
* ERROR_HANDLER is NULL, register it for the given CACHE in FS.
*/
static svn_error_t *
init_callbacks(svn_cache__t *cache,
svn_fs_t *fs,
svn_cache__error_handler_t error_handler,
apr_pool_t *pool)
{
if (cache != NULL)
{
#ifdef SVN_DEBUG_CACHE_DUMP_STATS
/* schedule printing the access statistics upon pool cleanup,
* i.e. end of FSFS session.
*/
struct dump_cache_baton_t *baton;
baton = apr_palloc(pool, sizeof(*baton));
baton->pool = pool;
baton->cache = cache;
apr_pool_cleanup_register(pool,
baton,
dump_cache_statistics,
apr_pool_cleanup_null);
#endif
if (error_handler)
SVN_ERR(svn_cache__set_error_handler(cache,
error_handler,
fs,
pool));
}
return SVN_NO_ERROR;
}
/* Sets *CACHE_P to cache instance based on provided options.
* Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if
* MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
* MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
* otherwise. Use the given PRIORITY class for the new cache. If it
* is 0, then use the default priority class. HAS_NAMESPACE indicates
* whether we prefixed this cache instance with a namespace.
*
* Unless NO_HANDLER is true, register an error handler that reports errors
* as warnings to the FS warning callback.
*
* Cache is allocated in RESULT_POOL, temporaries in SCRATCH_POOL.
* */
static svn_error_t *
create_cache(svn_cache__t **cache_p,
svn_memcache_t *memcache,
svn_membuffer_t *membuffer,
apr_int64_t pages,
apr_int64_t items_per_page,
svn_cache__serialize_func_t serializer,
svn_cache__deserialize_func_t deserializer,
apr_ssize_t klen,
const char *prefix,
apr_uint32_t priority,
svn_boolean_t has_namespace,
svn_fs_t *fs,
svn_boolean_t no_handler,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_cache__error_handler_t error_handler = no_handler
? NULL
: warn_and_fail_on_cache_errors;
if (priority == 0)
priority = SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY;
if (memcache)
{
SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
serializer, deserializer, klen,
prefix, result_pool));
error_handler = no_handler
? NULL
: warn_and_continue_on_cache_errors;
}
else if (membuffer)
{
/* We assume caches with namespaces to be relatively short-lived,
* i.e. their data will not be needed after a while. */
SVN_ERR(svn_cache__create_membuffer_cache(
cache_p, membuffer, serializer, deserializer,
klen, prefix, priority, FALSE, has_namespace,
result_pool, scratch_pool));
}
else if (pages)
{
SVN_ERR(svn_cache__create_inprocess(
cache_p, serializer, deserializer, klen, pages,
items_per_page, FALSE, prefix, result_pool));
}
else
{
*cache_p = NULL;
}
SVN_ERR(init_callbacks(*cache_p, fs, error_handler, result_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__initialize_caches(svn_fs_t *fs,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
const char *prefix = apr_pstrcat(pool,
"fsfs:", fs->uuid,
"/", normalize_key_part(fs->path, pool),
":",
SVN_VA_NULL);
svn_membuffer_t *membuffer;
svn_boolean_t no_handler = ffd->fail_stop;
svn_boolean_t cache_txdeltas;
svn_boolean_t cache_fulltexts;
svn_boolean_t cache_nodeprops;
const char *cache_namespace;
svn_boolean_t has_namespace;
/* Evaluating the cache configuration. */
SVN_ERR(read_config(&cache_namespace,
&cache_txdeltas,
&cache_fulltexts,
&cache_nodeprops,
fs,
pool));
prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, SVN_VA_NULL);
has_namespace = strlen(cache_namespace) > 0;
membuffer = svn_cache__get_global_membuffer_cache();
/* General rules for assigning cache priorities:
*
* - Data that can be reconstructed from other elements has low prio
* (e.g. fulltexts, directories etc.)
* - Index data required to find any of the other data has high prio
* (e.g. noderevs, L2P and P2L index pages)
* - everthing else should use default prio
*/
#ifdef SVN_DEBUG_CACHE_DUMP_STATS
/* schedule printing the global access statistics upon pool cleanup,
* i.e. when the repo instance gets closed / cleaned up.
*/
if (membuffer)
apr_pool_cleanup_register(fs->pool,
fs->pool,
dump_global_cache_statistics,
apr_pool_cleanup_null);
#endif
/* Make the cache for revision roots. For the vast majority of
* commands, this is only going to contain a few entries (svnadmin
* dump/verify is an exception here), so to reduce overhead let's
* try to keep it to just one page. I estimate each entry has about
* 130 bytes of overhead (svn_revnum_t key, ID struct, and the cache_entry);
* the default pool size is 8192, so about a fifty should fit comfortably.
*/
SVN_ERR(create_cache(&(ffd->rev_root_id_cache),
NULL,
membuffer,
1, 50,
svn_fs_fs__serialize_id,
svn_fs_fs__deserialize_id,
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "RRI", SVN_VA_NULL),
0,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* Rough estimate: revision DAG nodes have size around 1kBytes, so
* let's put 8 on a page. */
SVN_ERR(create_cache(&(ffd->rev_node_cache),
NULL,
membuffer,
1, 8,
svn_fs_fs__dag_serialize,
svn_fs_fs__dag_deserialize,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "DAG", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* 1st level DAG node cache */
ffd->dag_node_cache = svn_fs_fs__create_dag_cache(fs->pool);
/* Very rough estimate: 1K per directory. */
SVN_ERR(create_cache(&(ffd->dir_cache),
NULL,
membuffer,
1, 8,
svn_fs_fs__serialize_dir_entries,
svn_fs_fs__deserialize_dir_entries,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "DIR", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* 8 kBytes per entry (1000 revs / shared, one file offset per rev).
Covering about 8 pack files gives us an "o.k." hit rate. */
SVN_ERR(create_cache(&(ffd->packed_offset_cache),
NULL,
membuffer,
8, 1,
svn_fs_fs__serialize_manifest,
svn_fs_fs__deserialize_manifest,
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "PACK-MANIFEST",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* initialize node revision cache, if caching has been enabled */
SVN_ERR(create_cache(&(ffd->node_revision_cache),
NULL,
membuffer,
2, 16, /* ~500 byte / entry; 32 entries total */
svn_fs_fs__serialize_node_revision,
svn_fs_fs__deserialize_node_revision,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "NODEREVS", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* initialize representation header cache, if caching has been enabled */
SVN_ERR(create_cache(&(ffd->rep_header_cache),
NULL,
membuffer,
1, 200, /* ~40 bytes / entry; 200 entries total */
svn_fs_fs__serialize_rep_header,
svn_fs_fs__deserialize_rep_header,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "REPHEADER", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* initialize node change list cache, if caching has been enabled */
SVN_ERR(create_cache(&(ffd->changes_cache),
NULL,
membuffer,
1, 8, /* 1k / entry; 8 entries total, rarely used */
svn_fs_fs__serialize_changes,
svn_fs_fs__deserialize_changes,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "CHANGES", SVN_VA_NULL),
0,
has_namespace,
fs,
no_handler,
fs->pool, pool));
/* if enabled, cache revprops */
SVN_ERR(create_cache(&(ffd->revprop_cache),
NULL,
membuffer,
0, 0, /* Do not use inprocess cache */
svn_fs_fs__serialize_revprops,
svn_fs_fs__deserialize_revprops,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "REVPROP", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
TRUE, /* contents is short-lived */
fs,
no_handler,
fs->pool, pool));
/* if enabled, cache fulltext and other derived information */
if (cache_fulltexts)
{
SVN_ERR(create_cache(&(ffd->fulltext_cache),
ffd->memcache,
membuffer,
0, 0, /* Do not use the inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "TEXT", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
NULL,
membuffer,
0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_mergeinfo,
svn_fs_fs__deserialize_mergeinfo,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "MERGEINFO",
SVN_VA_NULL),
0,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
NULL,
membuffer,
0, 0, /* Do not use the inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
SVN_VA_NULL),
0,
has_namespace,
fs,
no_handler,
fs->pool, pool));
}
else
{
ffd->fulltext_cache = NULL;
ffd->mergeinfo_cache = NULL;
ffd->mergeinfo_existence_cache = NULL;
}
/* if enabled, cache node properties */
if (cache_nodeprops)
{
SVN_ERR(create_cache(&(ffd->properties_cache),
NULL,
membuffer,
0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_properties,
svn_fs_fs__deserialize_properties,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "PROP",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
}
else
{
ffd->properties_cache = NULL;
}
/* if enabled, cache text deltas and their combinations */
if (cache_txdeltas)
{
SVN_ERR(create_cache(&(ffd->raw_window_cache),
NULL,
membuffer,
0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_raw_window,
svn_fs_fs__deserialize_raw_window,
sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "RAW_WINDOW",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
NULL,
membuffer,
0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_txdelta_window,
svn_fs_fs__deserialize_txdelta_window,
sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->combined_window_cache),
NULL,
membuffer,
0, 0, /* Do not use the inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
}
else
{
ffd->txdelta_window_cache = NULL;
ffd->combined_window_cache = NULL;
}
SVN_ERR(create_cache(&(ffd->l2p_header_cache),
NULL,
membuffer,
8, 16, /* entry size varies but we must cover a
reasonable number of rev / pack files
to allow for delta chains to be walked
efficiently etc. */
svn_fs_fs__serialize_l2p_header,
svn_fs_fs__deserialize_l2p_header,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "L2P_HEADER",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->l2p_page_cache),
NULL,
membuffer,
8, 16, /* entry size varies but we must cover a
reasonable number of rev / pack files
to allow for delta chains to be walked
efficiently etc. */
svn_fs_fs__serialize_l2p_page,
svn_fs_fs__deserialize_l2p_page,
sizeof(svn_fs_fs__page_cache_key_t),
apr_pstrcat(pool, prefix, "L2P_PAGE",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->p2l_header_cache),
NULL,
membuffer,
4, 1, /* Large entries. Rarely used. */
svn_fs_fs__serialize_p2l_header,
svn_fs_fs__deserialize_p2l_header,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "P2L_HEADER",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->p2l_page_cache),
NULL,
membuffer,
4, 1, /* Variably sized entries. Rarely used. */
svn_fs_fs__serialize_p2l_page,
svn_fs_fs__deserialize_p2l_page,
sizeof(svn_fs_fs__page_cache_key_t),
apr_pstrcat(pool, prefix, "P2L_PAGE",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
has_namespace,
fs,
no_handler,
fs->pool, pool));
return SVN_NO_ERROR;
}
/* Baton to be used for the remove_txn_cache() pool cleanup function, */
struct txn_cleanup_baton_t
{
/* the cache to reset */
svn_cache__t *txn_cache;
/* the position where to reset it */
svn_cache__t **to_reset;
/* pool that TXN_CACHE was allocated in */
apr_pool_t *txn_pool;
/* pool that the FS containing the TO_RESET pointer was allocator */
apr_pool_t *fs_pool;
};
/* Forward declaration. */
static apr_status_t
remove_txn_cache_fs(void *baton_void);
/* APR pool cleanup handler that will reset the cache pointer given in
BATON_VOID when the TXN_POOL gets cleaned up. */
static apr_status_t
remove_txn_cache_txn(void *baton_void)
{
struct txn_cleanup_baton_t *baton = baton_void;
/* be careful not to hurt performance by resetting newer txn's caches. */
if (*baton->to_reset == baton->txn_cache)
{
/* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
*baton->to_reset = NULL;
}
/* It's cleaned up now. Prevent double cleanup. */
apr_pool_cleanup_kill(baton->fs_pool,
baton,
remove_txn_cache_fs);
return APR_SUCCESS;
}
/* APR pool cleanup handler that will reset the cache pointer given in
BATON_VOID when the FS_POOL gets cleaned up. */
static apr_status_t
remove_txn_cache_fs(void *baton_void)
{
struct txn_cleanup_baton_t *baton = baton_void;
/* be careful not to hurt performance by resetting newer txn's caches. */
if (*baton->to_reset == baton->txn_cache)
{
/* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
*baton->to_reset = NULL;
}
/* It's cleaned up now. Prevent double cleanup. */
apr_pool_cleanup_kill(baton->txn_pool,
baton,
remove_txn_cache_txn);
return APR_SUCCESS;
}
/* This function sets / registers the required callbacks for a given
* transaction-specific *CACHE object in FS, if CACHE is not NULL and
* a no-op otherwise. In particular, it will ensure that *CACHE gets
* reset to NULL upon POOL or FS->POOL destruction latest.
*/
static void
init_txn_callbacks(svn_fs_t *fs,
svn_cache__t **cache,
apr_pool_t *pool)
{
if (*cache != NULL)
{
struct txn_cleanup_baton_t *baton;
baton = apr_palloc(pool, sizeof(*baton));
baton->txn_cache = *cache;
baton->to_reset = cache;
baton->txn_pool = pool;
baton->fs_pool = fs->pool;
/* If any of these pools gets cleaned, we must reset the cache.
* We don't know which one will get cleaned up first, so register
* cleanup actions for both and during the cleanup action, unregister
* the respective other action. */
apr_pool_cleanup_register(pool,
baton,
remove_txn_cache_txn,
apr_pool_cleanup_null);
apr_pool_cleanup_register(fs->pool,
baton,
remove_txn_cache_fs,
apr_pool_cleanup_null);
}
}
svn_error_t *
svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
const char *txn_id,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
const char *prefix;
/* We don't support caching for concurrent transactions in the SAME
* FSFS session. Maybe, you forgot to clean POOL. */
if (ffd->txn_dir_cache != NULL || ffd->concurrent_transactions)
{
ffd->txn_dir_cache = NULL;
ffd->concurrent_transactions = TRUE;
return SVN_NO_ERROR;
}
/* Transaction content needs to be carefully prefixed to virtually
eliminate any chance for conflicts. The (repo, txn_id) pair
should be unique but if the filesystem format doesn't store the
global transaction ID via the txn-current file, and a transaction
fails, it might be possible to start a new transaction later that
receives the same id. For such older formats, throw in an uuid as
well -- just to be sure. */
if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
prefix = apr_pstrcat(pool,
"fsfs:", fs->uuid,
"/", fs->path,
":", txn_id,
":", "TXNDIR",
SVN_VA_NULL);
else
prefix = apr_pstrcat(pool,
"fsfs:", fs->uuid,
"/", fs->path,
":", txn_id,
":", svn_uuid_generate(pool),
":", "TXNDIR",
SVN_VA_NULL);
/* create a txn-local directory cache */
SVN_ERR(create_cache(&ffd->txn_dir_cache,
NULL,
svn_cache__get_global_membuffer_cache(),
1024, 8,
svn_fs_fs__serialize_txndir_entries,
svn_fs_fs__deserialize_dir_entries,
APR_HASH_KEY_STRING,
prefix,
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
TRUE, /* The TXN-ID is our namespace. */
fs,
TRUE,
pool, pool));
/* reset the transaction-specific cache if the pool gets cleaned up. */
init_txn_callbacks(fs, &(ffd->txn_dir_cache), pool);
return SVN_NO_ERROR;
}
void
svn_fs_fs__reset_txn_caches(svn_fs_t *fs)
{
/* we can always just reset the caches. This may degrade performance but
* can never cause in incorrect behavior. */
fs_fs_data_t *ffd = fs->fsap_data;
ffd->txn_dir_cache = NULL;
}