Merge from trunk to in-memory-cache, perhaps for the last time. git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/in-memory-cache@871452 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/Makefile.in b/Makefile.in index 8acbeab..e7eaec2 100644 --- a/Makefile.in +++ b/Makefile.in
@@ -46,6 +46,7 @@ SVN_SERF_LIBS = @SVN_SERF_LIBS@ SVN_SASL_LIBS = @SVN_SASL_LIBS@ SVN_ZLIB_LIBS = @SVN_ZLIB_LIBS@ +SVN_APR_MEMCACHE_LIBS = @SVN_APR_MEMCACHE_LIBS@ LIBS = @LIBS@ @@ -108,7 +109,7 @@ INCLUDES = -I$(top_srcdir)/subversion/include -I$(top_builddir)/subversion \ @SVN_APR_INCLUDES@ @SVN_APRUTIL_INCLUDES@ @SVN_GNOME_KEYRING_INCLUDES@ \ @SVN_KWALLET_INCLUDES@ @SVN_NEON_INCLUDES@ @SVN_SASL_INCLUDES@ \ - @SVN_SERF_INCLUDES@ @SVN_ZLIB_INCLUDES@ + @SVN_SERF_INCLUDES@ @SVN_ZLIB_INCLUDES@ @SVN_APR_MEMCACHE_INCLUDES@ APACHE_INCLUDES = @APACHE_INCLUDES@ APACHE_LIBEXECDIR = $(DESTDIR)@APACHE_LIBEXECDIR@ @@ -142,6 +143,9 @@ SVN_ZLIB_PREFIX = @SVN_ZLIB_PREFIX@ SVN_ZLIB_INCLUDES = @SVN_ZLIB_INCLUDES@ +SVN_APR_MEMCACHE_PREFIX = @SVN_APR_MEMCACHE_PREFIX@ +SVN_APR_MEMCACHE_INCLUDES = @SVN_APR_MEMCACHE_INCLUDES@ + MKDIR = @MKDIR@ # The EXTRA_ parameters can be used to pass extra flags at 'make' time. @@ -196,6 +200,7 @@ INSTALL_KWALLET_LIB = $(INSTALL_LIB) INSTALL_NEON_LIB = $(INSTALL_LIB) INSTALL_SERF_LIB = $(INSTALL_LIB) +INSTALL_APR_MEMCACHE_LIB = $(INSTALL_LIB) INSTALL_BIN = $(LIBTOOL) --mode=install $(INSTALL) INSTALL_CONTRIB = $(LIBTOOL) --mode=install $(INSTALL) INSTALL_TOOLS = $(LIBTOOL) --mode=install $(INSTALL) @@ -395,7 +400,9 @@ if test "$(PARALLEL)" != ""; then \ flags="--parallel $$flags"; \ fi; \ - $(PYTHON) $(top_srcdir)/build/run_tests.py $$flags \ + $(PYTHON) $(top_srcdir)/build/run_tests.py \ + --config-file $(top_srcdir)/subversion/tests/tests.conf \ + $$flags \ '$(abs_srcdir)' '$(abs_builddir)' $(TESTS); \ else \ echo "make check: Python 2.2 or greater is required,"; \
diff --git a/aclocal.m4 b/aclocal.m4 index d5485de..f902e54 100644 --- a/aclocal.m4 +++ b/aclocal.m4
@@ -20,6 +20,7 @@ sinclude(build/ac-macros/swig.m4) sinclude(build/ac-macros/sasl.m4) sinclude(build/ac-macros/zlib.m4) +sinclude(build/ac-macros/apr_memcache.m4) # Include the libtool macros sinclude(build/libtool.m4)
diff --git a/build.conf b/build.conf index 3c8a2b5..e48d4ab 100644 --- a/build.conf +++ b/build.conf
@@ -340,9 +340,9 @@ type = lib install = fsmod-lib path = subversion/libsvn_subr -libs = aprutil apriconv apr xml zlib +libs = aprutil apriconv apr xml zlib apr_memcache msvc-libs = advapi32.lib shfolder.lib ole32.lib -msvc-export = svn_auth.h svn_base64.h svn_cmdline.h svn_compat.h svn_config.h svn_ctype.h svn_dso.h svn_error.h svn_hash.h svn_io.h svn_md5.h svn_nls.h svn_opt.h svn_mergeinfo.h svn_path.h svn_pools.h svn_props.h svn_quoprint.h svn_sorts.h svn_string.h svn_subst.h svn_time.h svn_types.h svn_user.h svn_utf.h svn_version.h svn_xml.h private\svn_atomic.h private\svn_log.h private\svn_mergeinfo_private.h svn_iter.h private\svn_opt_private.h +msvc-export = svn_auth.h svn_base64.h svn_cmdline.h svn_compat.h svn_config.h svn_ctype.h svn_dso.h svn_error.h svn_hash.h svn_io.h svn_md5.h svn_nls.h svn_opt.h svn_mergeinfo.h svn_path.h svn_pools.h svn_props.h svn_quoprint.h svn_sorts.h svn_string.h svn_subst.h svn_time.h svn_types.h svn_user.h svn_utf.h svn_version.h svn_xml.h private\svn_atomic.h private\svn_log.h private\svn_mergeinfo_private.h svn_iter.h private\svn_opt_private.h svn_cache.h # Working copy management lib [libsvn_wc] @@ -639,6 +639,14 @@ # ---------------------------------------------------------------------------- # Tests for libsvn_subr +[cache-test] +description = Test in-memory cache +type = exe +path = subversion/tests/libsvn_subr +sources = cache-test.c +install = test +libs = libsvn_test libsvn_subr apr + [compat-test] description = Test compatibility functions type = exe @@ -870,6 +878,10 @@ msvc-libs = ws2_32.lib msvc-static = yes +[apr_memcache] +type = lib +external-lib = $(SVN_APR_MEMCACHE_LIBS) + [serf] type = lib external-lib = $(SVN_SERF_LIBS) @@ -904,7 +916,7 @@ fs-test fs-base-test skel-test key-test strings-reps-test changes-test locks-test repos-test compat-test config-test hashdump-test mergeinfo-test opt-test path-test stream-test - string-test time-test utf-test target-test error-test + string-test time-test utf-test target-test error-test cache-test revision-test translate-test random-test
diff --git a/build/ac-macros/apr_memcache.m4 b/build/ac-macros/apr_memcache.m4 new file mode 100644 index 0000000..c99cf13 --- /dev/null +++ b/build/ac-macros/apr_memcache.m4
@@ -0,0 +1,84 @@ +dnl +dnl SVN_LIB_APR_MEMCACHE +dnl +dnl Check configure options and assign variables related to +dnl the apr_memcache client library. +dnl Sets svn_lib_apr_memcache to "yes" if memcache code is accessible +dnl either from the standalone apr_memcache library or from apr-util. +dnl + +AC_DEFUN(SVN_LIB_APR_MEMCACHE, +[ + apr_memcache_found=no + + AC_ARG_WITH(apr_memcache,AC_HELP_STRING([--with-apr_memcache=PREFIX], + [Standalone apr_memcache client library]), + [ + if test "$withval" = "yes" ; then + AC_MSG_ERROR([--with-apr_memcache requires an argument.]) + else + AC_MSG_NOTICE([looking for separate apr_memcache package]) + apr_memcache_prefix=$withval + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$apr_memcache_prefix/include/apr_memcache-0" + AC_CHECK_HEADER(apr_memcache.h,[ + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS -L$apr_memcache_prefix/lib" + AC_CHECK_LIB(apr_memcache, apr_memcache_create, + [apr_memcache_found="standalone"]) + LDFLAGS="$save_ldflags"]) + CPPFLAGS="$save_cppflags" + fi + ], [ + if test -d "$srcdir/apr_memcache"; then + apr_memcache_found=reconfig + else +dnl Try just looking in apr-util (>= 1.3 has it already). + AC_MSG_NOTICE([looking for apr_memcache as part of apr-util]) + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES" + AC_CHECK_HEADER(apr_memcache.h,[ + save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS $SVN_APRUTIL_EXPORT_LIBS" + AC_CHECK_LIB(aprutil-1, apr_memcache_create, + [apr_memcache_found="aprutil"]) + LDFLAGS="$save_ldflags"]) + CPPFLAGS="$save_cppflags" + + fi + ]) + + + if test $apr_memcache_found = "reconfig"; then + SVN_EXTERNAL_PROJECT([apr_memcache], [--with-apr=$apr_config --with-apr-util=$apu_config]) + apr_memcache_prefix=$prefix + SVN_APR_MEMCACHE_PREFIX="$apr_memcache_prefix" + SVN_APR_MEMCACHE_INCLUDES="-I$srcdir/memcache" + SVN_APR_MEMCACHE_LIBS="$abs_builddir/memcache/libapr_memcache.la" + SVN_APR_MEMCACHE_EXPORT_LIBS="-L$apr_memcache_prefix/lib -lapr_memcache" + fi + + if test $apr_memcache_found = "standalone"; then + SVN_APR_MEMCACHE_PREFIX="$apr_memcache_prefix" + SVN_APR_MEMCACHE_INCLUDES="-I$apr_memcache_prefix/include/apr_memcache-0" + SVN_APR_MEMCACHE_LIBS="$apr_memcache_prefix/lib/libapr_memcache.la" + SVN_APR_MEMCACHE_EXPORT_LIBS="-L$apr_memcache_prefix/lib -lapr_memcache" + svn_lib_apr_memcache=yes + elif test $apr_memcache_found = "aprutil"; then +dnl We are already linking apr-util everywhere, so no special treatement needed. + SVN_APR_MEMCACHE_PREFIX="" + SVN_APR_MEMCACHE_INCLUDES="" + SVN_APR_MEMCACHE_LIBS="" + SVN_APR_MEMCACHE_EXPORT_LIBS="" + svn_lib_apr_memcache=yes + elif test $apr_memcache_found = "reconfig"; then + svn_lib_apr_memcache=yes + else + svn_lib_apr_memcache=no + fi + + AC_SUBST(SVN_APR_MEMCACHE_PREFIX) + AC_SUBST(SVN_APR_MEMCACHE_INCLUDES) + AC_SUBST(SVN_APR_MEMCACHE_LIBS) + AC_SUBST(SVN_APR_MEMCACHE_EXPORT_LIBS) +])
diff --git a/build/run_tests.py b/build/run_tests.py index 9cee4c9..4bcbe8a 100755 --- a/build/run_tests.py +++ b/build/run_tests.py
@@ -6,13 +6,13 @@ '''usage: python run_tests.py [--url=<base-url>] [--fs-type=<fs-type>] [--verbose] [--cleanup] [--enable-sasl] [--parallel] [--http-library=<http-library>] + [--config-file=<file>] [--server-minor-version=<version>] <abs_srcdir> <abs_builddir> <prog ...> -The optional base-url, fs-type, http-library, server-minor-version, -verbose, parallel, enable-sasl, and cleanup options, and the first two -parameters are passed unchanged to the TestHarness constructor. All -other parameters are names of test programs. +The optional flags and the first two parameters are passed unchanged +to the TestHarness constructor. All other parameters are names of +test programs. ''' import os, sys @@ -30,8 +30,8 @@ def __init__(self, abs_srcdir, abs_builddir, logfile, base_url=None, fs_type=None, http_library=None, server_minor_version=None, verbose=None, - cleanup=None, enable_sasl=None, parallel=None, list_tests=None, - svn_bin=None): + cleanup=None, enable_sasl=None, parallel=None, config_file=None, + list_tests=None, svn_bin=None): '''Construct a TestHarness instance. ABS_SRCDIR and ABS_BUILDDIR are the source and build directories. @@ -53,6 +53,9 @@ self.cleanup = cleanup self.enable_sasl = enable_sasl self.parallel = parallel + self.config_file = None + if config_file is not None: + self.config_file = os.path.abspath(config_file) self.list_tests = list_tests self.svn_bin = svn_bin self.log = None @@ -114,10 +117,14 @@ cmdline.append('--enable-sasl') if self.parallel is not None: cmdline.append('--parallel') + if self.config_file is not None: + cmdline.append(quote('--config-file=' + self.config_file)) elif os.access(prog, os.X_OK): progname = './' + progbase cmdline = [quote(progname), quote('--srcdir=' + os.path.join(self.srcdir, progdir))] + if self.config_file is not None: + cmdline.append(quote('--config-file=' + self.config_file)) else: print 'Don\'t know what to do about ' + progbase sys.exit(1) @@ -190,7 +197,7 @@ opts, args = my_getopt(sys.argv[1:], 'u:f:vc', ['url=', 'fs-type=', 'verbose', 'cleanup', 'http-library=', 'server-minor-version=', - 'enable-sasl', 'parallel']) + 'enable-sasl', 'parallel', 'config-file=']) except getopt.GetoptError: args = [] @@ -199,8 +206,8 @@ sys.exit(2) base_url, fs_type, verbose, cleanup, enable_sasl, http_library, \ - server_minor_version, parallel = \ - None, None, None, None, None, None, None, None + server_minor_version, parallel, config_file = \ + None, None, None, None, None, None, None, None, None for opt, val in opts: if opt in ['-u', '--url']: base_url = val @@ -218,13 +225,15 @@ enable_sasl = 1 elif opt in ['--parallel']: parallel = 1 + elif opt in ['--config-file']: + config_file = val else: raise getopt.GetoptError th = TestHarness(args[0], args[1], os.path.abspath('tests.log'), base_url, fs_type, http_library, server_minor_version, - verbose, cleanup, enable_sasl, parallel) + verbose, cleanup, enable_sasl, parallel, config_file) failed = th.run(args[2:]) if failed:
diff --git a/configure.ac b/configure.ac index 30b817f..bb1cc02 100644 --- a/configure.ac +++ b/configure.ac
@@ -89,6 +89,14 @@ dnl Search for serf as an alternative to neon SVN_LIB_SERF +dnl Search for apr_memcache (only affects fs_fs) +SVN_LIB_APR_MEMCACHE + +if test "$svn_lib_apr_memcache" = "yes"; then + AC_DEFINE(SVN_HAVE_MEMCACHE, 1, + [Defined if apr_memcache (standalone or in apr-util) is present]) +fi + dnl Set up a number of directories --------------------- dnl Create SVN_BINDIR for proper substitution
diff --git a/gen-make.py b/gen-make.py index 6fe722c..66a5d8b 100755 --- a/gen-make.py +++ b/gen-make.py
@@ -178,6 +178,9 @@ print " --vsnet-version=VER" print " generate for VS.NET version VER (2002, 2003, 2005 or 2008)" print " [only valid in combination with '-t vcproj']" + print + print " --with-apr_memcache=DIR" + print " the apr_memcache sources are in DIR" sys.exit(0) @@ -214,6 +217,7 @@ 'with-junit=', 'with-swig=', 'with-sasl=', + 'with-apr_memcache=', 'enable-pool-debug', 'enable-purify', 'enable-quantify',
diff --git a/subversion/bindings/swig/python/tests/mergeinfo.py b/subversion/bindings/swig/python/tests/mergeinfo.py index d59d4ed..ecd9a10 100644 --- a/subversion/bindings/swig/python/tests/mergeinfo.py +++ b/subversion/bindings/swig/python/tests/mergeinfo.py
@@ -39,7 +39,7 @@ self.tearDown() self.repos = repos.svn_repos_create(REPOS_PATH, '', '', None, None) repos.svn_repos_load_fs2(self.repos, dumpfile, StringIO(), - repos.svn_repos_load_uuid_default, '', + repos.svn_repos_load_uuid_ignore, '', 0, 0, None) self.fs = repos.fs(self.repos) self.rev = fs.youngest_rev(self.fs)
diff --git a/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py b/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py index 30b4e88..877876b 100644 --- a/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py +++ b/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py
@@ -54,7 +54,7 @@ r = repos.svn_repos_create(REPOS_PATH, '', '', None, None) repos.svn_repos_load_fs2(r, dumpfile, StringIO(), - repos.svn_repos_load_uuid_default, '', + repos.svn_repos_load_uuid_ignore, '', 0, 0, None) def tearDown(self):
diff --git a/subversion/include/svn_base64.h b/subversion/include/svn_base64.h index 679addc..ceb5e24 100644 --- a/subversion/include/svn_base64.h +++ b/subversion/include/svn_base64.h
@@ -55,7 +55,22 @@ /** Encode an @c svn_stringbuf_t into base64. * * A simple interface for encoding base64 data assuming we have all of - * it present at once. The returned string will be allocated from @c pool. + * it present at once. If @a break_lines is true, newlines will be + * inserted periodically; otherwise the string will only consist of + * base64 encoding characters. The returned string will be allocated + * from @c pool. + * + * @since New in 1.6. + */ +const svn_string_t *svn_base64_encode_string2(const svn_string_t *str, + svn_boolean_t break_lines, + apr_pool_t *pool); + +/** + * Same as svn_base64_encode_string2, but with @a break_lines always + * TRUE. + * + * @deprecated Provided for backward compatibility with the 1.5 API. */ const svn_string_t *svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool); @@ -63,7 +78,9 @@ /** Decode an @c svn_stringbuf_t from base64. * * A simple interface for decoding base64 data assuming we have all of - * it present at once. The returned string will be allocated from @c pool. + * it present at once. The returned string will be allocated from @c + * pool. + * */ const svn_string_t *svn_base64_decode_string(const svn_string_t *str, apr_pool_t *pool);
diff --git a/subversion/include/svn_cache.h b/subversion/include/svn_cache.h new file mode 100644 index 0000000..df4b77c --- /dev/null +++ b/subversion/include/svn_cache.h
@@ -0,0 +1,277 @@ +/** + * @copyright + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + * @endcopyright + * + * @file svn_cache.h + * @brief In-memory cache implementation. + */ + + +#ifndef SVN_CACHE_H +#define SVN_CACHE_H + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_iter.h" +#include "svn_config.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/** + * @defgroup svn_cache_support In-memory caching + * @{ + */ + +/** + * A function type for copying an object @a in into a different pool @pool + * and returning the result in @a *out. + * + * @since New in 1.6. +*/ +typedef svn_error_t *(svn_cache_dup_func_t)(void **out, + void *in, + apr_pool_t *pool); + +/** + * A function type for deserializing an object @a *out from the string + * @a data of length @a data_len in the pool @pool. + * + * @since New in 1.6. +*/ +typedef svn_error_t *(svn_cache_deserialize_func_t)(void **out, + const char *data, + apr_size_t data_len, + apr_pool_t *pool); + +/** + * A function type for serializing an object @a in into bytes. The + * function should allocate the serialized value in @a pool, set @a + * *data to the serialized value, and set *data_len to its length. + * + * @since New in 1.6. +*/ +typedef svn_error_t *(svn_cache_serialize_func_t)(char **data, + apr_size_t *data_len, + void *in, + apr_pool_t *pool); + +/** + * A function type for transforming or ignoring errors. @a pool may + * be used for temporary allocations. + * + * @since New in 1.6. + */ +typedef svn_error_t *(svn_cache_error_handler_t)(svn_error_t *err, + void *baton, + apr_pool_t *pool); + +/** + * A wrapper around apr_memcache_t, provided essentially so that the + * Subversion public API doesn't depend on whether or not you have + * access to the APR memcache libraries. + * + * @since New in 1.6. + */ +typedef struct svn_memcache_t svn_memcache_t; + +/** + * Opaque type for an in-memory cache. + * + * @since New in 1.6. + */ +typedef struct svn_cache_t svn_cache_t; + +/** + * Creates a new cache in @a *cache_p. This cache will use @a pool + * for all of its storage needs. The elements in the cache will be + * indexed by keys of length @a klen, which may be APR_HASH_KEY_STRING + * if they are strings. Cached values will be copied in and out of + * the cache using @a dup_func. + * + * The cache stores up to @a pages * @a items_per_page items at a + * time. The exact cache invalidation strategy is not defined here, + * but in general, a lower value for @a items_per_page means more + * memory overhead for the same number of items, but a higher value + * for @a items_per_page means more items are cleared at once. Both + * @a pages and @a items_per_page must be positive (though they both + * may certainly be 1). + * + * If @a thread_safe is true, and APR is compiled with threads, all + * accesses to the cache will be protected with a mutex. + * + * Note that NULL is a legitimate value for cache entries (and @a dup_func + * will not be called on it). + * + * It is not safe for @a dup_func to interact with the cache itself. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_create_inprocess(svn_cache_t **cache_p, + svn_cache_dup_func_t *dup_func, + apr_ssize_t klen, + apr_int64_t pages, + apr_int64_t items_per_page, + svn_boolean_t thread_safe, + apr_pool_t *pool); +/** + * Creates a new cache in @a *cache_p, communicating to a memcached + * process via @a memcache. The elements in the cache will be indexed + * by keys of length @a klen, which may be APR_HASH_KEY_STRING if they + * are strings. Values will be serialized for memcached using @a + * serialize_func and deserialized using @a deserialize_func. Because + * the same memcached server may cache many different kinds of values, + * @a prefix should be specified to differentiate this cache from + * other caches. @a *cache_p will be allocated in @a pool. + * + * If @a deserialize_func is NULL, then the data is returned as an + * svn_string_t; if @a serialize_func is NULL, then the data is + * assumed to be an svn_stringbuf_t. + * + * These caches are always thread safe. + * + * These caches do not support svn_cache_iter. + * + * If Subversion was not built with apr_memcache support, always + * raises SVN_ERR_NO_APR_MEMCACHE. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_create_memcache(svn_cache_t **cache_p, + svn_memcache_t *memcache, + svn_cache_serialize_func_t *serialize_func, + svn_cache_deserialize_func_t *deserialize_func, + apr_ssize_t klen, + const char *prefix, + apr_pool_t *pool); + +/** + * Given @a config, returns an APR memcached interface in @a + * *memcache_p allocated in @a pool if @a config contains entries in + * the SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS section describing + * memcached servers; otherwise, sets @a *memcache_p to NULL. + * + * If Subversion was not built with apr_memcache_support, then raises + * SVN_ERR_NO_APR_MEMCACHE if and only if @a config is configured to + * use memcache. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_make_memcache_from_config(svn_memcache_t **memcache_p, + svn_config_t *config, + apr_pool_t *pool); + +/** + * Sets @a handler to be @a cache's error handling routine. If any + * error is returned from a call to svn_cache_get or svn_cache_set, @a + * handler will be called with @a baton and the error, and the + * original function will return whatever error @a handler returns + * instead (possibly SVN_NO_ERROR); @a handler will receive the pool + * passed to the svn_cache_* function. @a pool is used for temporary + * allocations. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_set_error_handler(svn_cache_t *cache, + svn_cache_error_handler_t *handler, + void *baton, + apr_pool_t *pool); + + +#define SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "memcached-servers" + +/** + * Fetches a value indexed by @a key from @a cache into @a *value, + * setting @a *found to TRUE iff it is in the cache and FALSE if it is + * not found. The value is copied into @a pool using the copy + * function provided to the cache's constructor. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_get(void **value, + svn_boolean_t *found, + svn_cache_t *cache, + const void *key, + apr_pool_t *pool); + +/** + * Stores the value @value under the key @a key in @a cache. @a pool + * is used only for temporary allocations. The cache makes copies of + * @a key and @a value if necessary (that is, @a key and @a value may + * have shorter lifetimes than the cache). + * + * If there is already a value for @a key, this will replace it. Bear + * in mind that in some circumstances this may leak memory (that is, + * the cache's copy of the previous value may not be immediately + * cleared); it is only guaranteed to not leak for caches created with + * @a items_per_page equal to 1. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_set(svn_cache_t *cache, + const void *key, + void *value, + apr_pool_t *pool); + +/** + * Iterates over the elements currently in @a cache, calling @a func + * for each one until there are no more elements or @a func returns an + * error. Uses @a pool for temporary allocations. + * + * If @a completed is not NULL, then on return - if @a func returns no + * errors - @a *completed will be set to @c TRUE. + * + * If @a func returns an error other than @c SVN_ERR_ITER_BREAK, that + * error is returned. When @a func returns @c SVN_ERR_ITER_BREAK, + * iteration is interrupted, but no error is returned and @a + * *completed is set to @c FALSE. (The error handler set by + * svn_cache_set_error_handler is not used for svn_cache_iter.) + * + * It is not legal to perform any other cache operations on @a cache + * inside @a func. + * + * svn_cache_iter is not supported by all cache implementations; see + * the svn_cache_create_* function for details. + * + * @since New in 1.6. + */ +svn_error_t * +svn_cache_iter(svn_boolean_t *completed, + svn_cache_t *cache, + svn_iter_apr_hash_cb_t func, + void *baton, + apr_pool_t *pool); +/** @} */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_CACHE_H */
diff --git a/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h index 42f830e..b386ac6 100644 --- a/subversion/include/svn_error_codes.h +++ b/subversion/include/svn_error_codes.h
@@ -171,6 +171,10 @@ SVN_ERR_BAD_CATEGORY_START + 4, "Bogus mime-type") + SVN_ERRDEF(SVN_ERR_BAD_SERVER_SPECIFICATION, + SVN_ERR_BAD_CATEGORY_START + 5, + "Bogus server specification") + /** @since New in 1.5. * * Note that there was an unused slot sitting here at @@ -1153,6 +1157,16 @@ SVN_ERR_MISC_CATEGORY_START + 26, "Inquiry about unknown capability") + /** @since New in 1.6. */ + SVN_ERRDEF(SVN_ERR_TEST_SKIPPED, + SVN_ERR_MISC_CATEGORY_START + 27, + "Test skipped") + + /** @since New in 1.6. */ + SVN_ERRDEF(SVN_ERR_NO_APR_MEMCACHE, + SVN_ERR_MISC_CATEGORY_START + 28, + "apr memcache library not available") + /* command-line client errors */ SVN_ERRDEF(SVN_ERR_CL_ARG_PARSING_ERROR,
diff --git a/subversion/include/svn_io.h b/subversion/include/svn_io.h index 57f28d7..d7f5b91 100644 --- a/subversion/include/svn_io.h +++ b/subversion/include/svn_io.h
@@ -628,6 +628,12 @@ svn_stream_t *svn_stream_from_stringbuf(svn_stringbuf_t *str, apr_pool_t *pool); +/** Return a generic read-only stream connected to string @a str. + * Allocate the stream in @a pool. + */ +svn_stream_t *svn_stream_from_string(svn_string_t *str, + apr_pool_t *pool); + /** Return a stream that decompresses all data read and compresses all * data written. The stream @a stream is used to read and write all * compressed data. All compression data structures are allocated on
diff --git a/subversion/include/svn_path.h b/subversion/include/svn_path.h index d456c2f..09b5cea 100644 --- a/subversion/include/svn_path.h +++ b/subversion/include/svn_path.h
@@ -32,6 +32,7 @@ * - @c svn_path_canonicalize() * - @c svn_path_is_canonical() * - @c svn_path_internal_style() + * - @c svn_path_uri_encode() * * For the most part, we mean what most anyone would mean when talking * about canonical paths, but to be on the safe side, you must run @@ -459,7 +460,9 @@ /** Return @c TRUE iff @a path is URI-safe, @c FALSE otherwise. */ svn_boolean_t svn_path_is_uri_safe(const char *path); -/** Return a URI-encoded copy of @a path, allocated in @a pool. */ +/** Return a URI-encoded copy of @a path, allocated in @a pool. (@a + path can be an arbitrary UTF-8 string and does not have to be a + canonical path.) */ const char *svn_path_uri_encode(const char *path, apr_pool_t *pool); /** Return a URI-decoded copy of @a path, allocated in @a pool. */
diff --git a/subversion/libsvn_fs_fs/caching.c b/subversion/libsvn_fs_fs/caching.c new file mode 100644 index 0000000..ca6fcd6 --- /dev/null +++ b/subversion/libsvn_fs_fs/caching.c
@@ -0,0 +1,241 @@ +/* caching.c : in-memory caching + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include "fs.h" +#include "fs_fs.h" +#include "id.h" +#include "dag.h" +#include "../libsvn_fs/fs-loader.h" + +#include "svn_config.h" + +#include "svn_private_config.h" + +/*** Dup/serialize/deserialize functions. ***/ + + +/** Caching SVN_FS_ID_T values. **/ +static svn_cache_dup_func_t dup_id; +static svn_error_t * +dup_id(void **out, + void *in, + apr_pool_t *pool) +{ + svn_fs_id_t *id = in; + *out = svn_fs_fs__id_copy(id, pool); + return SVN_NO_ERROR; +} + +static svn_cache_serialize_func_t serialize_id; +static svn_error_t * +serialize_id(char **data, + apr_size_t *data_len, + void *in, + apr_pool_t *pool) +{ + svn_fs_id_t *id = in; + svn_string_t *id_str = svn_fs_fs__id_unparse(id, pool); + *data = (char *) id_str->data; + *data_len = id_str->len; + + return SVN_NO_ERROR; +} + + +static svn_cache_deserialize_func_t deserialize_id; +static svn_error_t * +deserialize_id(void **out, + const char *data, + apr_size_t data_len, + apr_pool_t *pool) +{ + svn_fs_id_t *id = svn_fs_fs__id_parse(data, data_len, pool); + if (id == NULL) + { + return svn_error_create(SVN_ERR_FS_NOT_ID, NULL, + _("Bad ID in cache")); + } + + *out = id; + return SVN_NO_ERROR; +} + + +/** Caching directory listings. **/ +static svn_cache_dup_func_t dup_dir_listing; +static svn_error_t * +dup_dir_listing(void **out, + void *in, + apr_pool_t *pool) +{ + apr_hash_t *new_entries = apr_hash_make(pool), *entries = in; + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) + { + void *val; + svn_fs_dirent_t *dirent, *new_dirent; + + apr_hash_this(hi, NULL, NULL, &val); + dirent = val; + new_dirent = apr_palloc(pool, sizeof(*new_dirent)); + new_dirent->name = apr_pstrdup(pool, dirent->name); + new_dirent->kind = dirent->kind; + new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool); + apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING, + new_dirent); + } + + *out = new_entries; + return SVN_NO_ERROR; +} + +/* Return a memcache in *MEMCACHE_P for FS if it's configured to use + memcached, or NULL otherwise. Also, sets *FAIL_STOP to a boolean + indicating whether cache errors should be returned to the caller or + just passed to the FS warning handler. Use FS->pool for allocating + the memcache, and POOL for temporary allocations. */ +svn_error_t * +read_config(svn_memcache_t **memcache_p, + svn_boolean_t *fail_stop, + svn_fs_t *fs, + apr_pool_t *pool) +{ + svn_config_t *config; + + SVN_ERR(svn_fs_fs__get_config(&config, fs, pool)); + SVN_ERR(svn_cache_make_memcache_from_config(memcache_p, config, + fs->pool)); + SVN_ERR(svn_config_get_bool(config, fail_stop, + CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP, + FALSE)); + + return SVN_NO_ERROR; + +} + + +static svn_cache_error_handler_t warn_on_cache_errors; +static svn_error_t * +warn_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; +} + + +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:", ffd->uuid, + "/", fs->path, ":", + NULL); + svn_memcache_t *memcache; + svn_boolean_t no_handler; + + SVN_ERR(read_config(&memcache, &no_handler, fs, pool)); + + /* 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 + * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t + + * id_private_t + 3 strings for value, and the cache_entry); the + * default pool size is 8192, so about a hundred should fit + * comfortably. */ + if (memcache) + SVN_ERR(svn_cache_create_memcache(&(ffd->rev_root_id_cache), + memcache, + serialize_id, + deserialize_id, + sizeof(svn_revnum_t), + apr_pstrcat(pool, prefix, "RRI", + NULL), + fs->pool)); + else + SVN_ERR(svn_cache_create_inprocess(&(ffd->rev_root_id_cache), + dup_id, sizeof(svn_revnum_t), + 1, 100, FALSE, fs->pool)); + if (! no_handler) + SVN_ERR(svn_cache_set_error_handler(ffd->rev_root_id_cache, + warn_on_cache_errors, fs, pool)); + + + /* Rough estimate: revision DAG nodes have size around 320 bytes, so + * let's put 16 on a page. */ + if (memcache) + SVN_ERR(svn_cache_create_memcache(&(ffd->rev_node_cache), + memcache, + svn_fs_fs__dag_serialize, + svn_fs_fs__dag_deserialize, + APR_HASH_KEY_STRING, + apr_pstrcat(pool, prefix, "DAG", + NULL), + fs->pool)); + else + SVN_ERR(svn_cache_create_inprocess(&(ffd->rev_node_cache), + svn_fs_fs__dag_dup_for_cache, + APR_HASH_KEY_STRING, + 1024, 16, FALSE, fs->pool)); + if (! no_handler) + SVN_ERR(svn_cache_set_error_handler(ffd->rev_node_cache, + warn_on_cache_errors, fs, pool)); + + + /* Very rough estimate: 1K per directory. */ + if (memcache) + SVN_ERR(svn_cache_create_memcache(&(ffd->dir_cache), + memcache, + svn_fs_fs__dir_entries_serialize, + svn_fs_fs__dir_entries_deserialize, + APR_HASH_KEY_STRING, + apr_pstrcat(pool, prefix, "DIR", + NULL), + fs->pool)); + else + SVN_ERR(svn_cache_create_inprocess(&(ffd->dir_cache), + dup_dir_listing, APR_HASH_KEY_STRING, + 1024, 8, FALSE, fs->pool)); + + if (! no_handler) + SVN_ERR(svn_cache_set_error_handler(ffd->dir_cache, + warn_on_cache_errors, fs, pool)); + + if (memcache) + SVN_ERR(svn_cache_create_memcache(&(ffd->fulltext_cache), + memcache, + NULL, NULL, /* Values are svn_string_t */ + APR_HASH_KEY_STRING, + apr_pstrcat(pool, prefix, "TEXT", + NULL), + fs->pool)); + else + ffd->fulltext_cache = NULL; + + if (! no_handler) + SVN_ERR(svn_cache_set_error_handler(ffd->fulltext_cache, + warn_on_cache_errors, fs, pool)); + + return SVN_NO_ERROR; +}
diff --git a/subversion/libsvn_fs_fs/dag.c b/subversion/libsvn_fs_fs/dag.c index 5e17e46..fa60a90 100644 --- a/subversion/libsvn_fs_fs/dag.c +++ b/subversion/libsvn_fs_fs/dag.c
@@ -98,6 +98,12 @@ return node->fs; } +void +svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs) +{ + node->fs = fs; +} + /* Dup NODEREV and all associated data into POOL. Leaves the id and is_fresh_txn_root fields as zero bytes. */ @@ -1073,6 +1079,137 @@ return new_node; } +svn_error_t * +svn_fs_fs__dag_dup_for_cache(void **out, + void *in, + apr_pool_t *pool) +{ + dag_node_t *in_node = in, *out_node; + out_node = svn_fs_fs__dag_dup(in_node, pool); + out_node->fs = NULL; + *out = out_node; + return SVN_NO_ERROR; +} + +/* The cache serialization format is: + * + * - For mutable nodes: the character 'M', then 'F' for files or 'D' + * for directories, then the ID, then '\n', then the created path. + * + * - For immutable nodes: the character 'I' followed by the noderev + * hash dump (the other fields can be reconstructed from this). (We + * assume that, once constructed, immutable nodes always contain + * their noderev.) + */ + +svn_error_t * +svn_fs_fs__dag_serialize(char **data, + apr_size_t *data_len, + void *in, + apr_pool_t *pool) +{ + dag_node_t *node = in; + svn_stringbuf_t *buf = svn_stringbuf_create("", pool); + + if (svn_fs_fs__dag_check_mutable(node)) + { + svn_stringbuf_appendcstr(buf, "M"); + svn_stringbuf_appendcstr(buf, (node->kind == svn_node_file ? "F" : "D")); + svn_stringbuf_appendcstr(buf, svn_fs_fs__id_unparse(node->id, + pool)->data); + svn_stringbuf_appendcstr(buf, "\n"); + svn_stringbuf_appendcstr(buf, node->created_path); + } + else + { + svn_stringbuf_appendcstr(buf, "I"); + SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_stringbuf(buf, pool), + node->node_revision, TRUE, pool)); + } + + *data = buf->data; + *data_len = buf->len; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__dag_deserialize(void **out, + const char *data, + apr_size_t data_len, + apr_pool_t *pool) +{ + dag_node_t *node = apr_pcalloc(pool, sizeof(*node)); + + if (data_len == 0) + return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, + _("Empty noderev in cache")); + + if (*data == 'M') + { + const char *newline; + int id_len; + + data++; data_len--; + if (data_len == 0) + return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, + _("Kindless noderev in cache")); + if (*data == 'F') + node->kind = svn_node_file; + else if (*data == 'D') + node->kind = svn_node_dir; + else + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Unknown kind for noderev in cache: '%c'"), + *data); + + data++; data_len--; + newline = memchr(data, '\n', data_len); + if (!newline) + return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, + _("Unterminated ID in cache")); + id_len = newline - 1 - data; + node->id = svn_fs_fs__id_parse(data, id_len, pool); + if (! node->id) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Bogus ID '%s' in cache"), + apr_pstrndup(pool, data, id_len)); + + data += id_len; data_len -= id_len; + data++; data_len--; + if (data_len == 0) + return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, + _("No created path")); + node->created_path = apr_pstrndup(pool, data, data_len); + } + else if (*data == 'I') + { + node_revision_t *noderev; + apr_pool_t *subpool = svn_pool_create(pool); + svn_stream_t *stream = + svn_stream_from_stringbuf(svn_stringbuf_ncreate(data + 1, + data_len - 1, + subpool), + subpool); + SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, pool)); + node->kind = noderev->kind; + node->id = svn_fs_fs__id_copy(noderev->id, pool); + node->created_path = apr_pstrdup(pool, noderev->created_path); + + if (noderev->is_fresh_txn_root) + node->fresh_root_predecessor_id = noderev->predecessor_id; + + node->node_revision = noderev; + + svn_pool_destroy(subpool); + } + else + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + _("Unknown node type in cache: '%c'"), *data); + + *out = node; + + return SVN_NO_ERROR; +} svn_error_t * svn_fs_fs__dag_open(dag_node_t **child_p,
diff --git a/subversion/libsvn_fs_fs/dag.h b/subversion/libsvn_fs_fs/dag.h index 7a2c4a4..79741c3 100644 --- a/subversion/libsvn_fs_fs/dag.h +++ b/subversion/libsvn_fs_fs/dag.h
@@ -20,8 +20,7 @@ #include "svn_fs.h" #include "svn_delta.h" - -#include "fs.h" +#include "svn_cache.h" #ifdef __cplusplus extern "C" { @@ -55,6 +54,7 @@ /* Generic DAG node stuff. */ +typedef struct dag_node_t dag_node_t; /* Fill *NODE with a dag_node_t representing node revision ID in FS, allocating in POOL. */ @@ -72,10 +72,23 @@ dag_node_t *svn_fs_fs__dag_dup(dag_node_t *node, apr_pool_t *pool); +/* Like svn_fs_fs__dag_dup, but implementing the svn_cache_dup_func_t + prototype, and NULLing the FS field. */ +svn_cache_dup_func_t svn_fs_fs__dag_dup_for_cache; + +/* Serialize a DAG node. */ +svn_cache_serialize_func_t svn_fs_fs__dag_serialize; + +/* Deserialize a DAG node. */ +svn_cache_deserialize_func_t svn_fs_fs__dag_deserialize; /* Return the filesystem containing NODE. */ svn_fs_t *svn_fs_fs__dag_get_fs(dag_node_t *node); +/* Changes the filesystem containing NODE to FS. (Used when pulling + nodes out of a shared cache, say.) */ +void svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs); + /* Set *REV to NODE's revision number, allocating in POOL. If NODE has never been committed as part of a revision, set *REV to
diff --git a/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c index d3cf428..afef0e7 100644 --- a/subversion/libsvn_fs_fs/fs.c +++ b/subversion/libsvn_fs_fs/fs.c
@@ -33,6 +33,7 @@ #include "fs_fs.h" #include "tree.h" #include "lock.h" +#include "id.h" #include "svn_private_config.h" #include "private/svn_fs_util.h" @@ -158,19 +159,13 @@ /* Creating a new filesystem. */ /* Set up vtable and fsap_data fields in FS. */ -static void +static svn_error_t * initialize_fs_struct(svn_fs_t *fs) { fs_fs_data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd)); fs->vtable = &fs_vtable; fs->fsap_data = ffd; - - ffd->rev_root_id_cache_pool = svn_pool_create(fs->pool); - ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool); - - ffd->rev_node_cache = apr_hash_make(fs->pool); - ffd->rev_node_list.prev = &ffd->rev_node_list; - ffd->rev_node_list.next = &ffd->rev_node_list; + return SVN_NO_ERROR; } /* This implements the fs_library_vtable_t.create() API. Create a new @@ -183,9 +178,11 @@ { SVN_ERR(svn_fs__check_fs(fs, FALSE)); - initialize_fs_struct(fs); + SVN_ERR(initialize_fs_struct(fs)); SVN_ERR(svn_fs_fs__create(fs, path, pool)); + + SVN_ERR(svn_fs_fs__initialize_caches(fs, pool)); return fs_serialized_init(fs, common_pool, pool); } @@ -201,9 +198,11 @@ fs_open(svn_fs_t *fs, const char *path, apr_pool_t *pool, apr_pool_t *common_pool) { - initialize_fs_struct(fs); + SVN_ERR(initialize_fs_struct(fs)); SVN_ERR(svn_fs_fs__open(fs, path, pool)); + + SVN_ERR(svn_fs_fs__initialize_caches(fs, pool)); return fs_serialized_init(fs, common_pool, pool); } @@ -242,8 +241,9 @@ apr_pool_t *common_pool) { SVN_ERR(svn_fs__check_fs(fs, FALSE)); - initialize_fs_struct(fs); + SVN_ERR(initialize_fs_struct(fs)); SVN_ERR(svn_fs_fs__open(fs, path, pool)); + SVN_ERR(svn_fs_fs__initialize_caches(fs, pool)); SVN_ERR(fs_serialized_init(fs, common_pool, pool)); return svn_fs_fs__upgrade(fs, pool); }
diff --git a/subversion/libsvn_fs_fs/fs.h b/subversion/libsvn_fs_fs/fs.h index 421be41..adaab5c 100644 --- a/subversion/libsvn_fs_fs/fs.h +++ b/subversion/libsvn_fs_fs/fs.h
@@ -25,6 +25,8 @@ #include <apr_network_io.h> #include "svn_fs.h" +#include "svn_cache.h" +#include "svn_config.h" #include "private/svn_fs_private.h" #ifdef __cplusplus @@ -50,6 +52,8 @@ #define PATH_TXN_CURRENT "txn-current" /* File with next txn key */ #define PATH_TXN_CURRENT_LOCK "txn-current-lock" /* Lock for txn-current */ #define PATH_LOCKS_DIR "locks" /* Directory of locks */ +/* If you change this, look at tests/svn_test_fs.c(maybe_install_fsfs_conf) */ +#define PATH_CONFIG "fsfs.conf" /* Configuration */ /* Names of special files and file extensions for transactions */ #define PATH_CHANGES "changes" /* Records changes made so far */ @@ -65,6 +69,10 @@ #define PATH_REV "rev" /* Proto rev file */ #define PATH_REV_LOCK "rev-lock" /* Proto rev (write) lock file */ +/* Names of sections and options in fsfs.conf. */ +#define CONFIG_SECTION_CACHES "caches" +#define CONFIG_OPTION_FAIL_STOP "fail-stop" + /* The format number of this filesystem. This is independent of the repository format number, and independent of any other FS back ends. */ @@ -91,15 +99,6 @@ noderev fields. */ #define SVN_FS_FS__MIN_MERGEINFO_FORMAT 3 -/* Maximum number of directories to cache dirents for. - This *must* be a power of 2 for DIR_CACHE_ENTRIES_MASK - to work. */ -#define NUM_DIR_CACHE_ENTRIES 128 -#define DIR_CACHE_ENTRIES_MASK(x) ((x) & (NUM_DIR_CACHE_ENTRIES - 1)) - -/* Maximum number of revroot ids to cache dirents for at a time. */ -#define NUM_RRI_CACHE_ENTRIES 4096 - /* Private FSFS-specific data shared between all svn_txn_t objects that relate to a particular transaction in a filesystem (as identified by transaction id and filesystem UUID). Objects of this type are @@ -164,32 +163,9 @@ apr_pool_t *common_pool; } fs_fs_shared_data_t; -typedef struct dag_node_t dag_node_t; - -/* Structure for DAG-node cache. Cache items are arranged in a - circular LRU list with a dummy entry, and also indexed with a hash - table. Transaction nodes are cached within the individual txn - roots; revision nodes are cached together within the FS object. */ -typedef struct dag_node_cache_t -{ - const char *key; /* Lookup key for cached node: path - for txns; rev catenated with path - for revs */ - dag_node_t *node; /* Cached node */ - struct dag_node_cache_t *prev; /* Next node in LRU list */ - struct dag_node_cache_t *next; /* Previous node in LRU list */ - apr_pool_t *pool; /* Pool in which node is allocated */ -} dag_node_cache_t; - - /* Private (non-shared) FSFS-specific data for each svn_fs_t object. */ typedef struct { - /* A cache of the last directory opened within the filesystem. */ - svn_fs_id_t *dir_cache_id[NUM_DIR_CACHE_ENTRIES]; - apr_hash_t *dir_cache[NUM_DIR_CACHE_ENTRIES]; - apr_pool_t *dir_cache_pool[NUM_DIR_CACHE_ENTRIES]; - /* The format number of this FS. */ int format; /* The maximum number of files to store per directory (for sharded @@ -202,23 +178,27 @@ /* The revision that was youngest, last time we checked. */ svn_revnum_t youngest_rev_cache; - /* Caches of immutable data. - - Both of these could be moved to fs_fs_shared_data_t to make them - last longer; on the other hand, this would require adding mutexes - for threaded builds. - */ + /* The fsfs.conf file, parsed. Allocated in FS->pool. */ + svn_config_t *config; - /* A cache of revision root IDs, allocated in this subpool. (IDs - are so small that one pool per ID would be overkill; - unfortunately, this means the only way we expire cache entries is - by wiping the whole cache.) */ - apr_hash_t *rev_root_id_cache; - apr_pool_t *rev_root_id_cache_pool; + /* Caches of immutable data. (Note that if these are created with + svn_cache_create_memcache, the data can be shared between + multiple svn_fs_t's for the same filesystem.) */ + + /* A cache of revision root IDs, mapping from (svn_revnum_t *) to + (svn_fs_id_t *). (Not threadsafe.) */ + svn_cache_t *rev_root_id_cache; /* DAG node cache for immutable nodes */ - dag_node_cache_t rev_node_list; - apr_hash_t *rev_node_cache; + svn_cache_t *rev_node_cache; + + /* A cache of the contents of immutable directories; maps from + unparsed FS ID to ###x. */ + svn_cache_t *dir_cache; + + /* Fulltext cache; currently only used with memcached. Maps from + rep key to svn_string_t. */ + svn_cache_t *fulltext_cache; /* Data shared between all svn_fs_t objects for a given filesystem. */ fs_fs_shared_data_t *shared;
diff --git a/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c index b59ef06..8b42699 100644 --- a/subversion/libsvn_fs_fs/fs_fs.c +++ b/subversion/libsvn_fs_fs/fs_fs.c
@@ -39,6 +39,7 @@ #include "svn_sorts.h" #include "svn_time.h" #include "svn_mergeinfo.h" +#include "svn_config.h" #include "fs.h" #include "err.h" @@ -982,6 +983,58 @@ return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT; } +svn_error_t * +svn_fs_fs__get_config(svn_config_t **config, + svn_fs_t *fs, + apr_pool_t *pool) +{ + fs_fs_data_t *ffd = fs->fsap_data; + + if (! ffd->config) + SVN_ERR(svn_config_read(&(ffd->config), + svn_path_join(fs->path, PATH_CONFIG, pool), + FALSE, + fs->pool)); + + *config = ffd->config; + return SVN_NO_ERROR; +} + +static svn_error_t * +write_config(svn_fs_t *fs, + apr_pool_t *pool) +{ +#define NL APR_EOL_STR + static const char * const fsfs_conf_contents = +"### This file controls the configuration of the FSFS filesystem." NL +"" NL +"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL +"### These options name memcached servers used to cache internal FSFS" NL +"### data. See http://www.danga.com/memcached/ for more information on" NL +"### memcached. To use memcached with FSFS, run one or more memcached" NL +"### servers, and specify each of them as an option like so:" NL +"# first-server = 127.0.0.1:11211" NL +"# remote-memcached = mymemcached.corp.example.com:11212" NL +"### The option name is ignored; the value is of the form HOST:PORT." NL +"### memcached servers can be shared between multiple repositories;" NL +"### however, if you do this, you *must* ensure that repositories have" NL +"### distinct UUIDs and paths, or else cached data from one repository" NL +"### might be used by another accidentally. Note also that memcached has" NL +"### no authentication for reads or writes, so you must sure that your" NL +"### memcached servers are only accessible by trusted users." NL +"" NL +"[" CONFIG_SECTION_CACHES "]" NL +"### When a cache-related error occurs, normally Subversion ignores it" NL +"### and continues, logging an error if the server is appropriately" NL +"### configured (and ignoring it with file:// access). To make" NL +"### Subversion never ignore cache errors, uncomment this line." NL +"# " CONFIG_OPTION_FAIL_STOP " = true" NL +; +#undef NL + return svn_io_file_create(svn_path_join(fs->path, PATH_CONFIG, pool), + fsfs_conf_contents, pool); +} + static svn_error_t * get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool); @@ -1364,39 +1417,31 @@ return SVN_NO_ERROR; } -/* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN - * bytes plus the stuff around them. */ -#define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1 - /* Given a revision file FILE that has been pre-positioned at the beginning of a Node-Rev header block, read in that header block and store it in the apr_hash_t HEADERS. All allocations will be from POOL. */ static svn_error_t * read_header_block(apr_hash_t **headers, - apr_file_t *file, + svn_stream_t *stream, apr_pool_t *pool) { *headers = apr_hash_make(pool); while (1) { - char header_str[MAX_HEADERS_STR_LEN]; + svn_stringbuf_t *header_str; const char *name, *value; - apr_size_t i = 0, header_len; - apr_size_t limit; - char *local_name, *local_value; + apr_size_t i = 0; + svn_boolean_t eof; - limit = sizeof(header_str); - SVN_ERR(svn_io_read_length_line(file, header_str, &limit, pool)); + SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool)); - if (strlen(header_str) == 0) + if (eof || header_str->len == 0) break; /* end of header block */ - header_len = strlen(header_str); - - while (header_str[i] != ':') + while (header_str->data[i] != ':') { - if (header_str[i] == '\0') + if (header_str->data[i] == '\0') return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Found malformed header in " "revision file")); @@ -1404,23 +1449,22 @@ } /* Create a 'name' string and point to it. */ - header_str[i] = '\0'; - name = header_str; + header_str->data[i] = '\0'; + name = header_str->data; /* Skip over the NULL byte and the space following it. */ i += 2; - if (i > header_len) + if (i > header_str->len) return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Found malformed header in " "revision file")); - value = header_str + i; + value = header_str->data + i; - local_name = apr_pstrdup(pool, name); - local_value = apr_pstrdup(pool, value); - - apr_hash_set(*headers, local_name, APR_HASH_KEY_STRING, local_value); + /* header_str is safely in our pool, so we can use bits of it as + key and value. */ + apr_hash_set(*headers, name, APR_HASH_KEY_STRING, value); } return SVN_NO_ERROR; @@ -1622,9 +1666,6 @@ apr_pool_t *pool) { apr_file_t *revision_file; - apr_hash_t *headers; - node_revision_t *noderev; - char *value; svn_error_t *err; if (svn_fs_fs__id_txn_id(id)) @@ -1653,7 +1694,22 @@ return err; } - SVN_ERR(read_header_block(&headers, revision_file, pool) ); + return svn_fs_fs__read_noderev(noderev_p, + svn_stream_from_aprfile2(revision_file, FALSE, + pool), + pool); +} + +svn_error_t * +svn_fs_fs__read_noderev(node_revision_t **noderev_p, + svn_stream_t *stream, + apr_pool_t *pool) +{ + apr_hash_t *headers; + node_revision_t *noderev; + char *value; + + SVN_ERR(read_header_block(&headers, stream, pool)); noderev = apr_pcalloc(pool, sizeof(*noderev)); @@ -1663,7 +1719,7 @@ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Missing id field in node-rev")); - SVN_ERR(svn_io_file_close(revision_file, pool)); + SVN_ERR(svn_stream_close(stream)); noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool); @@ -1687,7 +1743,7 @@ if (value) { SVN_ERR(read_rep_offsets(&noderev->prop_rep, value, - svn_fs_fs__id_txn_id(id), TRUE, pool)); + svn_fs_fs__id_txn_id(noderev->id), TRUE, pool)); } /* Get the data location. */ @@ -1695,7 +1751,7 @@ if (value) { SVN_ERR(read_rep_offsets(&noderev->data_rep, value, - svn_fs_fs__id_txn_id(id), + svn_fs_fs__id_txn_id(noderev->id), (noderev->kind == svn_node_dir), pool)); } @@ -1819,19 +1875,13 @@ pool)); } -/* Write the node-revision NODEREV into the file FILE. Only write - mergeinfo-related metadata if INCLUDE_MERGEINFO is true. Temporary - allocations are from POOL. */ -static svn_error_t * -write_noderev_txn(apr_file_t *file, - node_revision_t *noderev, - svn_boolean_t include_mergeinfo, - apr_pool_t *pool) + +svn_error_t * +svn_fs_fs__write_noderev(svn_stream_t *outfile, + node_revision_t *noderev, + svn_boolean_t include_mergeinfo, + apr_pool_t *pool) { - svn_stream_t *outfile; - - outfile = svn_stream_from_aprfile(file, pool); - SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n", svn_fs_fs__id_unparse(noderev->id, pool)->data)); @@ -1915,9 +1965,10 @@ APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED, APR_OS_DEFAULT, pool)); - SVN_ERR(write_noderev_txn(noderev_file, noderev, - svn_fs_fs__fs_supports_mergeinfo(fs), - pool)); + SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile(noderev_file, pool), + noderev, + svn_fs_fs__fs_supports_mergeinfo(fs), + pool)); SVN_ERR(svn_io_file_close(noderev_file, pool)); @@ -2013,7 +2064,8 @@ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); - SVN_ERR(read_header_block(&headers, rev_file, pool)); + SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile(rev_file, pool), + pool)); node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING); @@ -2120,21 +2172,14 @@ apr_off_t root_offset; svn_fs_id_t *root_id; svn_error_t *err; - const char *rev_str = apr_psprintf(pool, "%ld", rev); - svn_fs_id_t *cached_id; + svn_boolean_t is_cached; SVN_ERR(ensure_revision_exists(fs, rev, pool)); - /* Calculate an index into the revroot id cache */ - cached_id = apr_hash_get(ffd->rev_root_id_cache, - rev_str, - APR_HASH_KEY_STRING); - - if (cached_id) - { - *root_id_p = svn_fs_fs__id_copy(cached_id, pool); - return SVN_NO_ERROR; - } + SVN_ERR(svn_cache_get((void **) root_id_p, &is_cached, ffd->rev_root_id_cache, + &rev, pool)); + if (is_cached) + return SVN_NO_ERROR; err = svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool), APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool); @@ -2154,21 +2199,7 @@ SVN_ERR(svn_io_file_close(revision_file, pool)); - /* Make sure our cache size doesn't grow without bounds. */ - if (apr_hash_count(ffd->rev_root_id_cache) >= NUM_RRI_CACHE_ENTRIES) - { - /* In order to only use one pool for the whole cache, we need to - * completely wipe it to expire entries! */ - svn_pool_clear(ffd->rev_root_id_cache_pool); - ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool); - } - - /* Cache the answer, copying both the key and value into the cache's - pool. */ - apr_hash_set(ffd->rev_root_id_cache, - apr_pstrdup(ffd->rev_root_id_cache_pool, rev_str), - APR_HASH_KEY_STRING, - svn_fs_fs__id_copy(root_id, ffd->rev_root_id_cache_pool)); + SVN_ERR(svn_cache_set(ffd->rev_root_id_cache, &rev, root_id, pool)); *root_id_p = root_id; @@ -2419,6 +2450,12 @@ svn_filesize_t len; svn_filesize_t off; + /* The key for the fulltext cache for this rep, if there is a + fulltext cache. */ + const char *fulltext_cache_key; + /* The text we've been reading, if we're going to cache it. */ + svn_stringbuf_t *current_fulltext; + /* Used for temporary allocations during the read. */ apr_pool_t *pool; @@ -2428,12 +2465,15 @@ }; /* Create a rep_read_baton structure for node revision NODEREV in - filesystem FS and store it in *RB_P. Perform all allocations in + filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not + NULL, it is the rep's key in the fulltext cache, and a stringbuf + must be allocated to store the text. Perform all allocations in POOL. If rep is mutable, it must be for file contents. */ static svn_error_t * rep_read_get_baton(struct rep_read_baton **rb_p, svn_fs_t *fs, representation_t *rep, + const char *fulltext_cache_key, apr_pool_t *pool) { struct rep_read_baton *b; @@ -2447,9 +2487,15 @@ memcpy(b->checksum, rep->checksum, sizeof(b->checksum)); b->len = rep->expanded_size; b->off = 0; + b->fulltext_cache_key = fulltext_cache_key; b->pool = svn_pool_create(pool); b->filehandle_pool = svn_pool_create(pool); + if (fulltext_cache_key) + b->current_fulltext = svn_stringbuf_create("", b->filehandle_pool); + else + b->current_fulltext = NULL; + SVN_ERR(build_rep_list(&b->rs_list, &b->src_state, fs, rep, b->filehandle_pool)); @@ -2707,6 +2753,9 @@ /* Get the next block of data. */ SVN_ERR(get_contents(rb, buf, len)); + if (rb->current_fulltext) + svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len); + /* Perform checksumming. We want to check the checksum as soon as the last byte of data is read, in case the caller never performs a short read, but we don't want to finalize the MD5 context @@ -2731,9 +2780,31 @@ svn_md5_digest_to_cstring_display(checksum, rb->pool)); } } + + if (rb->off == rb->len && rb->current_fulltext) + { + fs_fs_data_t *ffd = rb->fs->fsap_data; + SVN_ERR(svn_cache_set(ffd->fulltext_cache, rb->fulltext_cache_key, + rb->current_fulltext, rb->pool)); + rb->current_fulltext = NULL; + } + return SVN_NO_ERROR; } + +/* Returns whether or not the expanded fulltext of the file is + * cacheable based on its size SIZE. Specifically, if it will fit + * into a memcached value. The memcached cutoff seems to be a bit + * (header length?) under a megabyte; we round down a little to be + * safe. + */ +static svn_boolean_t +fulltext_size_is_cacheable(svn_filesize_t size) +{ + return size < 1000000; +} + /* Return a stream in *CONTENTS_P that will read the contents of a representation stored at the location given by REP. Appropriate for any kind of immutable representation, but only for file @@ -2749,15 +2820,34 @@ representation_t *rep, apr_pool_t *pool) { - struct rep_read_baton *rb; - if (! rep) { *contents_p = svn_stream_empty(pool); } else { - SVN_ERR(rep_read_get_baton(&rb, fs, rep, pool)); + fs_fs_data_t *ffd = fs->fsap_data; + const char *fulltext_key = NULL; + struct rep_read_baton *rb; + + if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) + && fulltext_size_is_cacheable(rep->expanded_size)) + { + svn_string_t *fulltext; + svn_boolean_t is_cached; + fulltext_key = apr_psprintf(pool, "%ld/%" APR_OFF_T_FMT, + rep->revision, rep->offset); + SVN_ERR(svn_cache_get((void **) &fulltext, &is_cached, + ffd->fulltext_cache, fulltext_key, pool)); + if (is_cached) + { + *contents_p = svn_stream_from_string(fulltext, pool); + return SVN_NO_ERROR; + } + } + + SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_key, pool)); + *contents_p = svn_stream_create(rb, pool); svn_stream_set_read(*contents_p, rep_read_contents); svn_stream_set_close(*contents_p, rep_read_contents_close); @@ -2896,76 +2986,95 @@ return SVN_NO_ERROR; } -/* Return a copy of the directory hash ENTRIES in POOL. */ -static apr_hash_t * -copy_dir_entries(apr_hash_t *entries, - apr_pool_t *pool) + +static const char * +unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id, + apr_pool_t *pool) { - apr_hash_t *new_entries = apr_hash_make(pool); + return apr_psprintf(pool, "%s %s", + (kind == svn_node_file) ? KIND_FILE : KIND_DIR, + svn_fs_fs__id_unparse(id, pool)->data); +} + +/* Given a hash ENTRIES of dirent structions, return a hash in + *STR_ENTRIES_P, that has svn_string_t as the values in the format + specified by the fs_fs directory contents file. Perform + allocations in POOL. */ +static svn_error_t * +unparse_dir_entries(apr_hash_t **str_entries_p, + apr_hash_t *entries, + apr_pool_t *pool) +{ apr_hash_index_t *hi; + *str_entries_p = apr_hash_make(pool); + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) { + const void *key; + apr_ssize_t klen; void *val; - svn_fs_dirent_t *dirent, *new_dirent; + svn_fs_dirent_t *dirent; + const char *new_val; - apr_hash_this(hi, NULL, NULL, &val); + apr_hash_this(hi, &key, &klen, &val); dirent = val; - new_dirent = apr_palloc(pool, sizeof(*new_dirent)); - new_dirent->name = apr_pstrdup(pool, dirent->name); - new_dirent->kind = dirent->kind; - new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool); - apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING, - new_dirent); + new_val = unparse_dir_entry(dirent->kind, dirent->id, pool); + apr_hash_set(*str_entries_p, key, klen, + svn_string_create(new_val, pool)); } - return new_entries; + + return SVN_NO_ERROR; } svn_error_t * -svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p, - svn_fs_t *fs, - node_revision_t *noderev, - apr_pool_t *pool) +svn_fs_fs__dir_entries_serialize(char **data, + apr_size_t *data_len, + void *in, + apr_pool_t *pool) { - fs_fs_data_t *ffd = fs->fsap_data; - apr_hash_t *unparsed_entries, *parsed_entries; + apr_hash_t *entries = in; + svn_stringbuf_t *buf = svn_stringbuf_create("", pool); + svn_stream_t *stream = svn_stream_from_stringbuf(buf, pool); + + SVN_ERR(unparse_dir_entries(&entries, entries, pool)); + SVN_ERR(svn_hash_write2(entries, stream, SVN_HASH_TERMINATOR, pool)); + + *data = buf->data; + *data_len = buf->len; + + return SVN_NO_ERROR; +} + + +/* Given a hash STR_ENTRIES with values as svn_string_t as specified + in an FSFS directory contents listing, return a hash of dirents in + *ENTRIES_P. Perform allocations in POOL. */ +static svn_error_t * +parse_dir_entries(apr_hash_t **entries_p, + apr_hash_t *str_entries, + apr_pool_t *pool) +{ apr_hash_index_t *hi; - unsigned int hid; - /* Calculate an index into the dir entries cache. This should be - completely ignored if this is a mutable noderev. */ - hid = DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev->id)); - - /* If we have this directory cached, return it. */ - if (! svn_fs_fs__id_txn_id(noderev->id) && - ffd->dir_cache_id[hid] && svn_fs_fs__id_eq(ffd->dir_cache_id[hid], - noderev->id)) - { - *entries_p = copy_dir_entries(ffd->dir_cache[hid], pool); - return SVN_NO_ERROR; - } - - /* Read in the directory hash. */ - unparsed_entries = apr_hash_make(pool); - SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool)); - - parsed_entries = apr_hash_make(pool); + *entries_p = apr_hash_make(pool); /* Translate the string dir entries into real entries. */ - for (hi = apr_hash_first(pool, unparsed_entries); hi; hi = apr_hash_next(hi)) + for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi)) { const void *key; void *val; - char *str_val; + svn_string_t *str_val; char *str, *last_str; svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent)); apr_hash_this(hi, &key, NULL, &val); - str_val = apr_pstrdup(pool, *((char **)val)); + str_val = val; + str = apr_pstrdup(pool, str_val->data); dirent->name = apr_pstrdup(pool, key); - str = apr_strtok(str_val, " ", &last_str); + str = apr_strtok(str, " ", &last_str); if (str == NULL) return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Directory entry corrupt")); @@ -2991,26 +3100,61 @@ dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool); - apr_hash_set(parsed_entries, dirent->name, APR_HASH_KEY_STRING, dirent); + apr_hash_set(*entries_p, dirent->name, APR_HASH_KEY_STRING, dirent); } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__dir_entries_deserialize(void **out, + const char *data, + apr_size_t data_len, + apr_pool_t *pool) +{ + apr_hash_t *entries = apr_hash_make(pool); + svn_stringbuf_t *buf = svn_stringbuf_ncreate(data, data_len, pool); + svn_stream_t *stream = svn_stream_from_stringbuf(buf, pool); + + SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool)); + SVN_ERR(parse_dir_entries(&entries, entries, pool)); + + *out = entries; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p, + svn_fs_t *fs, + node_revision_t *noderev, + apr_pool_t *pool) +{ + fs_fs_data_t *ffd = fs->fsap_data; + const char *unparsed_id; + apr_hash_t *unparsed_entries, *parsed_entries; + + /* Are we looking for an immutable directory? We could try the + * cache. */ + if (! svn_fs_fs__id_txn_id(noderev->id)) + { + svn_boolean_t found; + + unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data; + SVN_ERR(svn_cache_get((void **) entries_p, &found, ffd->dir_cache, + unparsed_id, pool)); + if (found) + return SVN_NO_ERROR; + } + + /* Read in the directory hash. */ + unparsed_entries = apr_hash_make(pool); + SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool)); + SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries, pool)); + /* If this is an immutable directory, let's cache the contents. */ if (! svn_fs_fs__id_txn_id(noderev->id)) - { - /* Start by NULLing the ID field, so that we never leave the - cache in an illegal state. */ - ffd->dir_cache_id[hid] = NULL; - - if (ffd->dir_cache_pool[hid]) - svn_pool_clear(ffd->dir_cache_pool[hid]); - else - ffd->dir_cache_pool[hid] = svn_pool_create(fs->pool); - - ffd->dir_cache[hid] = copy_dir_entries(parsed_entries, - ffd->dir_cache_pool[hid]); - ffd->dir_cache_id[hid] = svn_fs_fs__id_copy(noderev->id, - ffd->dir_cache_pool[hid]); - } + SVN_ERR(svn_cache_set(ffd->dir_cache, unparsed_id, parsed_entries, pool)); *entries_p = parsed_entries; return SVN_NO_ERROR; @@ -4076,15 +4220,8 @@ svn_fs_fs__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool) { - fs_fs_data_t *ffd; - SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); - /* Clean out the directory cache. */ - ffd = txn->fs->fsap_data; - memset(&ffd->dir_cache_id, 0, - sizeof(svn_fs_id_t *) * NUM_DIR_CACHE_ENTRIES); - /* Now, purge the transaction. */ SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool), _("Transaction cleanup failed")); @@ -4093,47 +4230,6 @@ } -static const char * -unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id, - apr_pool_t *pool) -{ - return apr_psprintf(pool, "%s %s", - (kind == svn_node_file) ? KIND_FILE : KIND_DIR, - svn_fs_fs__id_unparse(id, pool)->data); -} - -/* Given a hash ENTRIES of dirent structions, return a hash in - *STR_ENTRIES_P, that has svn_string_t as the values in the format - specified by the fs_fs directory contents file. Perform - allocations in POOL. */ -static svn_error_t * -unparse_dir_entries(apr_hash_t **str_entries_p, - apr_hash_t *entries, - apr_pool_t *pool) -{ - apr_hash_index_t *hi; - - *str_entries_p = apr_hash_make(pool); - - for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) - { - const void *key; - apr_ssize_t klen; - void *val; - svn_fs_dirent_t *dirent; - const char *new_val; - - apr_hash_this(hi, &key, &klen, &val); - dirent = val; - new_val = unparse_dir_entry(dirent->kind, dirent->id, pool); - apr_hash_set(*str_entries_p, key, klen, - svn_string_create(new_val, pool)); - } - - return SVN_NO_ERROR; -} - - svn_error_t * svn_fs_fs__set_entry(svn_fs_t *fs, const char *txn_id, @@ -4868,9 +4964,10 @@ noderev->id = new_id; /* Write out our new node-revision. */ - SVN_ERR(write_noderev_txn(file, noderev, - svn_fs_fs__fs_supports_mergeinfo(fs), - pool)); + SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile(file, pool), + noderev, + svn_fs_fs__fs_supports_mergeinfo(fs), + pool)); /* Return our ID that references the revision file. */ *new_id_p = noderev->id; @@ -5462,6 +5559,8 @@ "", pool)); } + SVN_ERR(write_config(fs, pool)); + /* This filesystem is ready. Stamp it with a format number. */ SVN_ERR(write_format(path_format(fs, pool), ffd->format, ffd->max_files_per_dir, FALSE, pool)); @@ -5582,7 +5681,8 @@ apr_pool_t *iterpool; SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); - SVN_ERR(read_header_block(&headers, rev_file, pool)); + SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile(rev_file, pool), + pool)); /* We're going to populate a skeletal noderev - just the id and data_rep. */ value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
diff --git a/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h index 1f0f17c..1b37e86 100644 --- a/subversion/libsvn_fs_fs/fs_fs.h +++ b/subversion/libsvn_fs_fs/fs_fs.h
@@ -60,6 +60,23 @@ svn_boolean_t fresh_txn_root, apr_pool_t *pool); +/* Write the node-revision NODEREV into the stream OUTFILE. Only write + mergeinfo-related metadata if INCLUDE_MERGEINFO is true. Temporary + allocations are from POOL. */ +svn_error_t * +svn_fs_fs__write_noderev(svn_stream_t *outfile, + node_revision_t *noderev, + svn_boolean_t include_mergeinfo, + apr_pool_t *pool); + +/* Reads the node-revision *NODEREV from the stream STREAM. Temporary + allocations are from POOL. */ +svn_error_t * +svn_fs_fs__read_noderev(node_revision_t **noderev, + svn_stream_t *stream, + apr_pool_t *pool); + + /* Set *YOUNGEST to the youngest revision in filesystem FS. Do any temporary allocation in POOL. */ svn_error_t *svn_fs_fs__youngest_rev(svn_revnum_t *youngest, @@ -73,6 +90,12 @@ svn_revnum_t rev, apr_pool_t *pool); +/* Serialize a directory contents hash. */ +svn_cache_serialize_func_t svn_fs_fs__dir_entries_serialize; + +/* Deserialize a directory contents hash. */ +svn_cache_deserialize_func_t svn_fs_fs__dir_entries_deserialize; + /* Set *ENTRIES to an apr_hash_t of dirent structs that contain the directory entries of node-revision NODEREV in filesystem FS. The returned table (and its keys and values) is allocated in POOL, @@ -180,6 +203,14 @@ /* Return whether or not the given FS supports mergeinfo metadata. */ svn_boolean_t svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs); +/* Sets *CONFIG to the parsed version of FS's fsfs.conf, allocated + in FS->pool. POOL is used for temporary allocations. */ +svn_error_t * +svn_fs_fs__get_config(svn_config_t **config, + svn_fs_t *fs, + apr_pool_t *pool); + + /* Store a transaction record in *TXN_P for the transaction identified by TXN_ID in filesystem FS. Allocate everything from POOL. */ svn_error_t *svn_fs_fs__get_txn(transaction_t **txn_p, @@ -469,4 +500,10 @@ apr_pool_t *pool); +/* Sets up the svn_cache_t structures in FS. POOL is used for + temporary allocations. */ +svn_error_t * +svn_fs_fs__initialize_caches(svn_fs_t *fs, apr_pool_t *pool); + + #endif
diff --git a/subversion/libsvn_fs_fs/structure b/subversion/libsvn_fs_fs/structure index 3ff4b8f..daef181 100644 --- a/subversion/libsvn_fs_fs/structure +++ b/subversion/libsvn_fs_fs/structure
@@ -54,6 +54,7 @@ txn-current-lock Empty file, locked to serialise 'txn-current' uuid File containing the UUID of the repository format File containing the format number of this filesystem + fsfs.conf Configuration file Files in the revprops directory are in the hash dump format used by svn_hash_write. @@ -86,6 +87,10 @@ performs on this file is "get and increment"; the "txn-current-lock" file is locked during this operation. +"fsfs.conf" is a configuration file in the standard Subversion/Python +config format. It is automatically generated when you create a new +repository; read the generated file for details on what it controls. + Filesystem formats ------------------
diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c index bb15649..f9524a3 100644 --- a/subversion/libsvn_fs_fs/tree.c +++ b/subversion/libsvn_fs_fs/tree.c
@@ -76,10 +76,6 @@ to the database (which also generates more log-files). */ #define WRITE_BUFFER_SIZE 512000 -/* The maximum number of cache items to maintain in the node cache. */ -#define TXN_NODE_CACHE_MAX_KEYS 32 -#define REV_NODE_CACHE_MAX_KEYS 128 - /* The root structures. @@ -111,9 +107,9 @@ typedef struct { - /* Dummy entry for circular LRU cache, and associated hash table. */ - dag_node_cache_t txn_node_list; - apr_hash_t *txn_node_cache; + /* Cache of txn DAG nodes (without their nested noderevs, because + * it's mutable). */ + svn_cache_t *txn_node_cache; } fs_txn_root_data_t; /* Declared here to resolve the circular dependencies. */ @@ -124,16 +120,18 @@ dag_node_t *root_dir, apr_pool_t *pool); -static svn_fs_root_t *make_txn_root(svn_fs_t *fs, const char *txn, - svn_revnum_t base_rev, apr_uint32_t flags, - apr_pool_t *pool); +static svn_error_t *make_txn_root(svn_fs_root_t **root_p, + svn_fs_t *fs, const char *txn, + svn_revnum_t base_rev, apr_uint32_t flags, + apr_pool_t *pool); /*** Node Caching ***/ +/* Find and return the DAG node cache for ROOT and the key that + should be used for PATH. */ static void -locate_cache(dag_node_cache_t **node_list, - apr_hash_t **node_cache, +locate_cache(svn_cache_t **cache, const char **key, svn_fs_root_t *root, const char *path, @@ -142,148 +140,130 @@ if (root->is_txn_root) { fs_txn_root_data_t *frd = root->fsap_data; - *node_list = &frd->txn_node_list; - *node_cache = frd->txn_node_cache; - *key = path; + if (cache) *cache = frd->txn_node_cache; + if (key && path) *key = path; } else { fs_fs_data_t *ffd = root->fs->fsap_data; - *node_list = &ffd->rev_node_list; - *node_cache = ffd->rev_node_cache; - *key = apr_psprintf(pool, "%ld%s", - root->rev, path); + if (cache) *cache = ffd->rev_node_cache; + if (key && path) *key = apr_psprintf(pool, "%ld%s", + root->rev, path); } } /* Return NODE for PATH from ROOT's node cache, or NULL if the node - isn't cached. */ -static dag_node_t * -dag_node_cache_get(svn_fs_root_t *root, + isn't cached; the node is copied into POOL. */ +static svn_error_t * +dag_node_cache_get(dag_node_t **node_p, + svn_fs_root_t *root, const char *path, apr_pool_t *pool) { - dag_node_cache_t *item, *node_list; - apr_hash_t *node_cache; + svn_boolean_t found; + dag_node_t *node; + svn_cache_t *cache; const char *key; /* Assert valid input. */ assert(*path == '/'); - locate_cache(&node_list, &node_cache, &key, - root, path, pool); + locate_cache(&cache, &key, root, path, pool); - /* Look in the cache for our desired item. */ - item = apr_hash_get(node_cache, key, APR_HASH_KEY_STRING); - if (item && item->node) + SVN_ERR(svn_cache_get((void **) &node, &found, cache, key, pool)); + if (found && node) { - /* Move this cache item to the front of the LRU list. */ - item->prev->next = item->next; - item->next->prev = item->prev; - item->prev = node_list; - item->next = node_list->next; - item->prev->next = item; - item->next->prev = item; - - /* Return the cached node. */ - return svn_fs_fs__dag_dup(item->node, pool); + /* Patch up the FS, since this might have come from an old FS + * object. */ + svn_fs_fs__dag_set_fs(node, root->fs); + *node_p = node; } - - return NULL; + else + *node_p = NULL; + return SVN_NO_ERROR; } /* Add the NODE for PATH to ROOT's node cache. */ -static void +static svn_error_t * dag_node_cache_set(svn_fs_root_t *root, const char *path, dag_node_t *node, - apr_pool_t *temp_pool) + apr_pool_t *pool) { - dag_node_cache_t *item, *node_list; - apr_hash_t *node_cache; + svn_cache_t *cache; const char *key; - apr_pool_t *pool; - int max_keys = root->is_txn_root - ? TXN_NODE_CACHE_MAX_KEYS : REV_NODE_CACHE_MAX_KEYS; - - /* The pool passed to this function can *only* be used for - short-term calculations, not for the actual cache value! - - To ensure that our cache values live as long as the svn_fs_root_t - in which they are ultimately stored, and to allow us to free() - them individually without harming the rest, they are each - allocated from a subpool of ROOT's pool. We'll keep one subpool - around for each cache slot -- as we start expiring stuff - to make room for more entries, we'll re-use the expired thing's - pool. */ /* Assert valid input and state. */ assert(*path == '/'); - locate_cache(&node_list, &node_cache, &key, - root, path, temp_pool); + locate_cache(&cache, &key, root, path, pool); - /* If we have an existing entry for this path, reuse it. */ - item = apr_hash_get(node_cache, key, APR_HASH_KEY_STRING); - - /* Otherwise, if the cache is full, reuse the tail of the LRU list. */ - if (!item && apr_hash_count(node_cache) == max_keys) - item = node_list->prev; - - if (item) - { - /* Remove the existing item from the cache and reuse its pool. */ - item->prev->next = item->next; - item->next->prev = item->prev; - apr_hash_set(node_cache, item->key, APR_HASH_KEY_STRING, NULL); - pool = item->pool; - svn_pool_clear(pool); - } - else - { - /* Allocate a new pool. */ - apr_pool_t *parent_pool = root->is_txn_root ? root->pool : root->fs->pool; - pool = svn_pool_create(parent_pool); - } - - /* Create and fill in the cache item. */ - item = apr_palloc(pool, sizeof(*item)); - item->key = apr_pstrdup(pool, key); - item->node = svn_fs_fs__dag_dup(node, pool); - item->pool = pool; - - /* Link it into the head of the LRU list and hash table. */ - item->prev = node_list; - item->next = node_list->next; - item->prev->next = item; - item->next->prev = item; - apr_hash_set(node_cache, item->key, APR_HASH_KEY_STRING, item); + return svn_cache_set(cache, key, node, pool); } -/* Invalidate cache entries for PATH and any of its children. */ -static void -dag_node_cache_invalidate(svn_fs_root_t *root, - const char *path) +/* Baton for find_descendents_in_cache. */ +struct fdic_baton { + const char *path; + apr_array_header_t *list; + apr_pool_t *pool; +}; + +/* If the given item is a descendent of BATON->PATH, push + * it onto BATON->LIST (copying into BATON->POOL). Implements + * the svn_iter_apr_hash_cb_t prototype. */ +static svn_error_t * +find_descendents_in_cache(void *baton, + const void *key, + apr_ssize_t klen, + void *val, + apr_pool_t *pool) { - fs_txn_root_data_t *frd; - apr_size_t len = strlen(path); - const char *key; - dag_node_cache_t *item; + struct fdic_baton *b = baton; + const char *item_path = key; + + if (svn_path_is_ancestor(b->path, item_path)) + APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path); + + return SVN_NO_ERROR; +} + +/* Invalidate cache entries for PATH and any of its children. This + should *only* be called on a transaction root! */ +static svn_error_t * +dag_node_cache_invalidate(svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + struct fdic_baton b; + svn_cache_t *cache; + apr_pool_t *iterpool; + int i; + + b.path = path; + b.pool = svn_pool_create(pool); + b.list = apr_array_make(b.pool, 1, sizeof(const char *)); assert(root->is_txn_root); + locate_cache(&cache, NULL, root, NULL, b.pool); - frd = root->fsap_data; - for (item = frd->txn_node_list.next; - item != &frd->txn_node_list; - item = item->next) + SVN_ERR(svn_cache_iter(NULL, cache, find_descendents_in_cache, + &b, b.pool)); + + iterpool = svn_pool_create(b.pool); + + for (i = 0; i < b.list->nelts; i++) { - key = item->key; - if (strncmp(key, path, len) == 0 && (key[len] == '/' || !key[len])) - item->node = NULL; + const char *descendent = APR_ARRAY_IDX(b.list, i, const char *); + svn_pool_clear(iterpool); + SVN_ERR(svn_cache_set(cache, descendent, NULL, iterpool)); } + + svn_pool_destroy(iterpool); + svn_pool_destroy(b.pool); + return SVN_NO_ERROR; } @@ -295,7 +275,6 @@ svn_fs_txn_t *txn, apr_pool_t *pool) { - svn_fs_root_t *root; apr_uint32_t flags = 0; apr_hash_t *txnprops; @@ -312,11 +291,7 @@ flags |= SVN_FS_TXN_CHECK_LOCKS; } - root = make_txn_root(txn->fs, txn->id, txn->base_rev, flags, pool); - - *root_p = root; - - return SVN_NO_ERROR; + return make_txn_root(root_p, txn->fs, txn->id, txn->base_rev, flags, pool); } @@ -661,7 +636,7 @@ /* If we found a directory entry, follow it. First, we check our node cache, and, failing that, we hit the DAG layer. */ - cached_node = dag_node_cache_get(root, path_so_far, pool); + SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, pool)); if (cached_node) child = cached_node; else @@ -706,7 +681,7 @@ /* Cache the node we found (if it wasn't already cached). */ if (! cached_node) - dag_node_cache_set(root, path_so_far, child, pool); + SVN_ERR(dag_node_cache_set(root, path_so_far, child, pool)); } /* Are we finished traversing the path? */ @@ -808,8 +783,8 @@ pool)); /* Update the path cache. */ - dag_node_cache_set(root, parent_path_path(parent_path, pool), clone, - pool); + SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), + clone, pool)); } else { @@ -834,13 +809,13 @@ apr_pool_t *pool) { parent_path_t *parent_path; - dag_node_t *node = NULL; + dag_node_t *node; /* Canonicalize the input PATH. */ path = svn_fs__canonicalize_abspath(path, pool); - /* If ROOT is a revision root, we'll look for the DAG in our cache. */ - node = dag_node_cache_get(root, path, pool); + /* First we look for the DAG in our cache. */ + SVN_ERR(dag_node_cache_get(&node, root, path, pool)); if (! node) { /* Call open_path with no flags, as we want this to return an error @@ -1907,7 +1882,8 @@ pool)); /* Add this directory to the path cache. */ - dag_node_cache_set(root, parent_path_path(parent_path, pool), sub_dir, pool); + SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), + sub_dir, pool)); /* Make a record of this modification in the changes table. */ SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(sub_dir), @@ -1956,7 +1932,8 @@ txn_id, pool)); /* Remove this node and any children from the path cache. */ - dag_node_cache_invalidate(root, parent_path_path(parent_path, pool)); + SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path, pool), + pool)); /* Update mergeinfo counts for parents */ if (svn_fs_fs__fs_supports_mergeinfo(root->fs) && mergeinfo_count > 0) @@ -2095,8 +2072,9 @@ txn_id, pool)); if (kind == svn_fs_path_change_replace) - dag_node_cache_invalidate(to_root, parent_path_path(to_parent_path, - pool)); + SVN_ERR(dag_node_cache_invalidate(to_root, + parent_path_path(to_parent_path, + pool), pool)); if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs) && mergeinfo_start != mergeinfo_end) @@ -2256,7 +2234,8 @@ pool)); /* Add this file to the path cache. */ - dag_node_cache_set(root, parent_path_path(parent_path, pool), child, pool); + SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), child, + pool)); /* Make a record of this modification in the changes table. */ SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(child), @@ -3786,8 +3765,9 @@ /* Construct a root object referring to the root of the transaction named TXN and based on revision BASE_REV in FS, with FLAGS to describe transaction's behavior. Create the new root in POOL. */ -static svn_fs_root_t * -make_txn_root(svn_fs_t *fs, +static svn_error_t * +make_txn_root(svn_fs_root_t **root_p, + svn_fs_t *fs, const char *txn, svn_revnum_t base_rev, apr_uint32_t flags, @@ -3801,11 +3781,18 @@ root->txn_flags = flags; root->rev = base_rev; - frd->txn_node_cache = apr_hash_make(root->pool); - frd->txn_node_list.prev = &frd->txn_node_list; - frd->txn_node_list.next = &frd->txn_node_list; + /* Because this cache actually tries to invalidate elements, keep + the number of elements per page down. + + Note that since dag_node_cache_invalidate uses svn_cache_iter, + this *cannot* be a memcache-based cache. */ + SVN_ERR(svn_cache_create_inprocess(&(frd->txn_node_cache), + svn_fs_fs__dag_dup_for_cache, + APR_HASH_KEY_STRING, + 32, 20, FALSE, root->pool)); root->fsap_data = frd; - return root; + *root_p = root; + return SVN_NO_ERROR; }
diff --git a/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c index 6d855f4..d2786f8 100644 --- a/subversion/libsvn_ra_local/ra_plugin.c +++ b/subversion/libsvn_ra_local/ra_plugin.c
@@ -408,6 +408,28 @@ return schemes; } +/* Do nothing. + * + * Why is this acceptable? As of now, FS warnings are used for only + * two things: failures to close BDB repositories and failures to + * interact with memcached in FSFS (new in 1.6). In 1.5 and earlier, + * we did not call svn_fs_set_warning_func in ra_local, which means + * that any BDB-closing failure would have led to abort()s; the fact + * that this hasn't led to huge hues and cries makes it seem likely + * that this just doesn't happen that often, at least not through + * ra_local. And as far as memcached goes, it seems unlikely that + * somebody is going to go through the trouble of setting up and + * running memcached servers but then use ra_local access. So we + * ignore errors here, so that memcached can use the FS warnings API + * without crashing ra_local. + */ +static void +ignore_warnings(void *baton, + svn_error_t *err) +{ + return; +} + static svn_error_t * svn_ra_local__open(svn_ra_session_t *session, const char *repos_URL, @@ -439,6 +461,9 @@ convenience. */ sess->fs = svn_repos_fs(sess->repos); + /* Ignore FS warnings. */ + svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL); + /* Cache the repository UUID as well */ SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
diff --git a/subversion/libsvn_subr/cache-inprocess.c b/subversion/libsvn_subr/cache-inprocess.c new file mode 100644 index 0000000..cef78cd --- /dev/null +++ b/subversion/libsvn_subr/cache-inprocess.c
@@ -0,0 +1,461 @@ +/* + * cache-inprocess.c: in-memory caching for Subversion + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include <assert.h> + +#include <apr_thread_mutex.h> + +#include "svn_pools.h" + +#include "svn_private_config.h" + +#include "cache.h" + +/* The (internal) cache object. */ +typedef struct { + /* Maps from a key (of size CACHE->KLEN) to a struct cache_entry. */ + apr_hash_t *hash; + apr_ssize_t klen; + + /* Used to copy values in and out of the cache. */ + svn_cache_dup_func_t *dup_func; + + /* The number of pages we're allowed to allocate before having to + * try to reuse one. */ + apr_int64_t unallocated_pages; + /* Number of cache entries stored on each page. Must be at least 1. */ + apr_int64_t items_per_page; + + /* A dummy cache_page serving as the head of a circular doubly + * linked list of cache_pages. SENTINEL->NEXT is the most recently + * used page, and SENTINEL->PREV is the least recently used page. + * All pages in this list are "full"; the page currently being + * filled (PARTIAL_PAGE) is not in the list. */ + struct cache_page *sentinel; + + /* A page currently being filled with entries, or NULL if there's no + * partially-filled page. This page is not in SENTINEL's list. */ + struct cache_page *partial_page; + /* If PARTIAL_PAGE is not null, this is the number of entries + * currently on PARTIAL_PAGE. */ + apr_int64_t partial_page_number_filled; + + /* The pool that the svn_cache_t itself, HASH, and all pages are + * allocated in; subpools of this pool are used for the cache_entry + * structs, as well as the dup'd values and hash keys. + */ + apr_pool_t *cache_pool; + +#if APR_HAS_THREADS + /* A lock for intra-process synchronization to the cache, or NULL if + * the cache's creator doesn't feel the cache needs to be + * thread-safe. */ + apr_thread_mutex_t *mutex; +#endif +} inprocess_cache_t; + +/* A cache page; all items on the page are allocated from the same + * pool. */ +struct cache_page { + /* Pointers for the LRU list anchored at the cache's SENTINEL. + * (NULL for the PARTIAL_PAGE.) */ + struct cache_page *prev; + struct cache_page *next; + + /* The pool in which cache_entry structs, hash keys, and dup'd + * values are allocated. */ + apr_pool_t *page_pool; + + /* A singly linked list of the entries on this page; used to remove + * them from the cache's HASH before reusing the page. */ + struct cache_entry *first_entry; +}; + +/* An cache entry. */ +struct cache_entry { + const void *key; + void *value; + + /* The page it's on (needed so that the LRU list can be + * maintained). */ + struct cache_page *page; + + /* Next entry on the page. */ + struct cache_entry *next_entry; +}; + + +/* Removes PAGE from the doubly-linked list it is in (leaving its PREV + * and NEXT fields undefined). */ +static void +remove_page_from_list(struct cache_page *page) +{ + page->prev->next = page->next; + page->next->prev = page->prev; +} + +/* Inserts PAGE after CACHE's sentinel. */ +static void +insert_page(inprocess_cache_t *cache, + struct cache_page *page) +{ + struct cache_page *pred = cache->sentinel; + + page->prev = pred; + page->next = pred->next; + page->prev->next = page; + page->next->prev = page; +} + +/* If PAGE is in the circularly linked list (eg, its NEXT isn't NULL), + * move it to the front of the list. */ +static void +move_page_to_front(inprocess_cache_t *cache, + struct cache_page *page) +{ + assert(page != cache->sentinel); + + if (! page->next) + return; + + remove_page_from_list(page); + insert_page(cache, page); +} + +/* Uses CACHE->dup_func to copy VALUE into *VALUE_P inside POOL, or + just sets *VALUE_P to NULL if VALUE is NULL. */ +static svn_error_t * +duplicate_value(void **value_p, + inprocess_cache_t *cache, + void *value, + apr_pool_t *pool) +{ + if (value) + SVN_ERR((cache->dup_func)(value_p, value, pool)); + else + *value_p = NULL; + return SVN_NO_ERROR; +} + +/* Return a copy of KEY inside POOL, using CACHE->KLEN to figure out + * how. */ +static const void * +duplicate_key(inprocess_cache_t *cache, + const void *key, + apr_pool_t *pool) +{ + if (cache->klen == APR_HASH_KEY_STRING) + return apr_pstrdup(pool, key); + else + return apr_pmemdup(pool, key, cache->klen); +} + +/* If applicable, locks CACHE's mutex. */ +static svn_error_t * +lock_cache(inprocess_cache_t *cache) +{ +#if APR_HAS_THREADS + apr_status_t status; + if (! cache->mutex) + return SVN_NO_ERROR; + + status = apr_thread_mutex_lock(cache->mutex); + if (status) + return svn_error_wrap_apr(status, _("Can't lock cache mutex")); +#endif + + return SVN_NO_ERROR; +} + +/* If applicable, unlocks CACHE's mutex, then returns ERR. */ +static svn_error_t * +unlock_cache(inprocess_cache_t *cache, + svn_error_t *err) +{ +#if APR_HAS_THREADS + apr_status_t status; + if (! cache->mutex) + return err; + + status = apr_thread_mutex_unlock(cache->mutex); + if (status && !err) + return svn_error_wrap_apr(status, _("Can't unlock cache mutex")); +#endif + + return err; +} + +svn_error_t * +inprocess_cache_get(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *pool) +{ + inprocess_cache_t *cache = cache_void; + struct cache_entry *entry; + svn_error_t *err; + + SVN_ERR(lock_cache(cache)); + + entry = apr_hash_get(cache->hash, key, cache->klen); + if (! entry) + { + *found = FALSE; + return unlock_cache(cache, SVN_NO_ERROR); + } + + move_page_to_front(cache, entry->page); + + *found = TRUE; + err = duplicate_value(value_p, cache, entry->value, pool); + return unlock_cache(cache, err); +} + +/* Removes PAGE from the LRU list, removes all of its entries from + * CACHE's hash, clears its pool, and sets its entry pointer to NULL. + * Finally, puts it in the "partial page" slot in the cache and sets + * partial_page_number_filled to 0. Must be called on a page actually + * in the list. */ +static void +erase_page(inprocess_cache_t *cache, + struct cache_page *page) +{ + struct cache_entry *e; + + remove_page_from_list(page); + + for (e = page->first_entry; + e; + e = e->next_entry) + { + apr_hash_set(cache->hash, e->key, cache->klen, NULL); + } + + svn_pool_clear(page->page_pool); + + page->first_entry = NULL; + page->prev = NULL; + page->next = NULL; + + cache->partial_page = page; + cache->partial_page_number_filled = 0; +} + + +svn_error_t * +inprocess_cache_set(void *cache_void, + const void *key, + void *value, + apr_pool_t *pool) +{ + inprocess_cache_t *cache = cache_void; + struct cache_entry *existing_entry; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR(lock_cache(cache)); + + existing_entry = apr_hash_get(cache->hash, key, cache->klen); + + /* Is it already here, but we can do the one-item-per-page + * optimization? */ + if (existing_entry && cache->items_per_page == 1) + { + /* Special case! ENTRY is the *only* entry on this page, so + * why not wipe it (so as not to leak the previous value). + */ + struct cache_page *page = existing_entry->page; + + /* This can't be the partial page: items_per_page == 1 + * *never* has a partial page (except for in the temporary state + * that we're about to fake). */ + assert(page->next != NULL); + assert(cache->partial_page == NULL); + + erase_page(cache, page); + existing_entry = NULL; + } + + /* Is it already here, and we just have to leak the old value? */ + if (existing_entry) + { + struct cache_page *page = existing_entry->page; + + move_page_to_front(cache, page); + err = duplicate_value(&(existing_entry->value), cache, + value, page->page_pool); + goto cleanup; + } + + /* Do we not have a partial page to put it on, but we are allowed to + * allocate more? */ + if (cache->partial_page == NULL && cache->unallocated_pages > 0) + { + cache->partial_page = apr_pcalloc(cache->cache_pool, + sizeof(*(cache->partial_page))); + cache->partial_page->page_pool = svn_pool_create(cache->cache_pool); + cache->partial_page_number_filled = 0; + (cache->unallocated_pages)--; + } + + /* Do we really not have a partial page to put it on, even after the + * one-item-per-page optimization and checking the unallocated page + * count? */ + if (cache->partial_page == NULL) + { + struct cache_page *oldest_page = cache->sentinel->prev; + + assert(oldest_page != cache->sentinel); + + /* Erase the page and put it in cache->partial_page. */ + erase_page(cache, oldest_page); + } + + assert(cache->partial_page != NULL); + + { + struct cache_page *page = cache->partial_page; + struct cache_entry *new_entry = apr_pcalloc(page->page_pool, + sizeof(*new_entry)); + + /* Copy the key and value into the page's pool. */ + new_entry->key = duplicate_key(cache, key, page->page_pool); + err = duplicate_value(&(new_entry->value), cache, value, + page->page_pool); + if (err) + goto cleanup; + + /* Add the entry to the page's list. */ + new_entry->page = page; + new_entry->next_entry = page->first_entry; + page->first_entry = new_entry; + + /* Add the entry to the hash, using the *entry's* copy of the + * key. */ + apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry); + + /* We've added something else to the partial page. */ + (cache->partial_page_number_filled)++; + + /* Is it full? */ + if (cache->partial_page_number_filled >= cache->items_per_page) + { + insert_page(cache, page); + cache->partial_page = NULL; + } + } + + cleanup: + return unlock_cache(cache, err); +} + +/* Baton type for svn_cache_iter. */ +struct cache_iter_baton { + svn_iter_apr_hash_cb_t user_cb; + void *user_baton; +}; + +/* Call the user's callback with the actual value, not the + cache_entry. Implements the svn_iter_apr_hash_cb_t + prototype. */ +static svn_error_t * +iter_cb(void *baton, + const void *key, + apr_ssize_t klen, + void *val, + apr_pool_t *pool) +{ + struct cache_iter_baton *b = baton; + struct cache_entry *entry = val; + return (b->user_cb)(b->user_baton, key, klen, entry->value, pool); +} + +svn_error_t * +inprocess_cache_iter(svn_boolean_t *completed, + void *cache_void, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *pool) +{ + inprocess_cache_t *cache = cache_void; + struct cache_iter_baton b; + b.user_cb = user_cb; + b.user_baton = user_baton; + + SVN_ERR(lock_cache(cache)); + return unlock_cache(cache, + svn_iter_apr_hash(completed, cache->hash, iter_cb, &b, + pool)); + +} + +static svn_cache__vtable_t inprocess_cache_vtable = { + inprocess_cache_get, + inprocess_cache_set, + inprocess_cache_iter +}; + +svn_error_t * +svn_cache_create_inprocess(svn_cache_t **cache_p, + svn_cache_dup_func_t *dup_func, + apr_ssize_t klen, + apr_int64_t pages, + apr_int64_t items_per_page, + svn_boolean_t thread_safe, + apr_pool_t *pool) +{ + svn_cache_t *wrapper = apr_pcalloc(pool, sizeof(*wrapper)); + inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache)); + + cache->hash = apr_hash_make(pool); + cache->klen = klen; + + cache->dup_func = dup_func; + + assert(pages >= 1); + cache->unallocated_pages = pages; + assert(items_per_page >= 1); + cache->items_per_page = items_per_page; + + cache->sentinel = apr_pcalloc(pool, sizeof(*(cache->sentinel))); + cache->sentinel->prev = cache->sentinel; + cache->sentinel->next = cache->sentinel; + /* The sentinel doesn't need a pool. (We're happy to crash if we + * accidentally try to treat it like a real page.) */ + +#if APR_HAS_THREADS + if (thread_safe) + { + apr_status_t status = apr_thread_mutex_create(&(cache->mutex), + APR_THREAD_MUTEX_DEFAULT, + pool); + if (status) + return svn_error_wrap_apr(status, + _("Can't create cache mutex")); + } +#endif + + cache->cache_pool = pool; + + wrapper->vtable = &inprocess_cache_vtable; + wrapper->cache_internal = cache; + wrapper->error_handler = wrapper->error_baton = NULL; + + *cache_p = wrapper; + return SVN_NO_ERROR; +}
diff --git a/subversion/libsvn_subr/cache-memcache.c b/subversion/libsvn_subr/cache-memcache.c new file mode 100644 index 0000000..bf0af81 --- /dev/null +++ b/subversion/libsvn_subr/cache-memcache.c
@@ -0,0 +1,417 @@ +/* + * cache-memcache.c: memcached caching for Subversion + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include <assert.h> + +#include "svn_cache.h" +#include "svn_pools.h" +#include "svn_base64.h" +#include "svn_md5.h" +#include "svn_path.h" + +#include "svn_private_config.h" + +#include "cache.h" + +#if SVN_HAVE_MEMCACHE + +#include <apr_memcache.h> + +/* A note on thread safety: + + The apr_memcache_t object does its own mutex handling, and nothing + else in memcache_t is ever modified, so this implementation should + be fully thread-safe. +*/ + +/* The (internal) cache object. */ +typedef struct { + /* The memcached server set we're using. */ + apr_memcache_t *memcache; + + /* A prefix used to differentiate our data from any other data in + * the memcached (URI-encoded). */ + const char *prefix; + + /* The size of the key: either a fixed number of bytes or + * APR_HASH_KEY_STRING. */ + apr_ssize_t klen; + + + /* Used to marshal values in and out of the cache. */ + svn_cache_serialize_func_t *serialize_func; + svn_cache_deserialize_func_t *deserialize_func; +} memcache_t; + +/* The wrapper around apr_memcache_t. */ +struct svn_memcache_t { + apr_memcache_t *c; +}; + + +/* The memcached protocol says the maximum key length is 250. Let's + just say 249, to be safe. */ +#define MAX_MEMCACHED_KEY_LEN 249 +#define MEMCACHED_KEY_UNHASHED_LEN (MAX_MEMCACHED_KEY_LEN - \ + 2 * APR_MD5_DIGESTSIZE) + + +/* Returns a memcache key for the given key KEY for CACHE, allocated + in POOL. */ +const char * +build_key(memcache_t *cache, + const void *raw_key, + apr_pool_t *pool) +{ + const char *encoded_suffix; + const char *long_key; + apr_size_t long_key_len; + + if (cache->klen == APR_HASH_KEY_STRING) + encoded_suffix = svn_path_uri_encode(raw_key, pool); + else + { + const svn_string_t *raw = svn_string_ncreate(raw_key, cache->klen, pool); + const svn_string_t *encoded = svn_base64_encode_string2(raw, FALSE, + pool); + encoded_suffix = encoded->data; + } + + long_key = apr_pstrcat(pool, "SVN:", cache->prefix, ":", encoded_suffix, + NULL); + long_key_len = strlen(long_key); + + /* We don't want to have a key that's too big. If it was going to + be too big, we MD5 the entire string, then replace the last bit + with the checksum. Note that APR_MD5_DIGESTSIZE is for the pure + binary digest; we have to double that when we convert to hex. + + Every key we use will either be at most + MEMCACHED_KEY_UNHASHED_LEN bytes long, or be exactly + MAX_MEMCACHED_KEY_LEN bytes long. */ + if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN) + { + unsigned char digest[APR_MD5_DIGESTSIZE]; + apr_md5(digest, long_key, long_key_len); + + long_key = apr_pstrcat(pool, + apr_pstrmemdup(pool, long_key, + MEMCACHED_KEY_UNHASHED_LEN), + svn_md5_digest_to_cstring_display(digest, pool), + NULL); + } + + return long_key; +} + + +svn_error_t * +memcache_get(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *pool) +{ + memcache_t *cache = cache_void; + apr_status_t apr_err; + char *data; + const char *mc_key; + apr_size_t data_len; + apr_pool_t *subpool = svn_pool_create(pool); + + mc_key = build_key(cache, key, subpool); + + apr_err = apr_memcache_getp(cache->memcache, + (cache->deserialize_func ? subpool : pool), + mc_key, + &data, + &data_len, + NULL /* ignore flags */); + if (apr_err == APR_NOTFOUND) + { + *found = FALSE; + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } + else if (apr_err != APR_SUCCESS || !data) + return svn_error_wrap_apr(apr_err, + _("Unknown memcached error while reading")); + + /* We found it! */ + if (cache->deserialize_func) + { + SVN_ERR((cache->deserialize_func)(value_p, data, data_len, pool)); + } + else + { + svn_string_t *value = apr_pcalloc(pool, sizeof(*value)); + value->data = data; + value->len = data_len; + *value_p = value; + } + *found = TRUE; + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +svn_error_t * +memcache_set(void *cache_void, + const void *key, + void *value, + apr_pool_t *pool) +{ + memcache_t *cache = cache_void; + apr_pool_t *subpool = svn_pool_create(pool); + char *data; + const char *mc_key = build_key(cache, key, subpool); + apr_size_t data_len; + apr_status_t apr_err; + + if (cache->serialize_func) + { + SVN_ERR((cache->serialize_func)(&data, &data_len, value, subpool)); + } + else + { + svn_stringbuf_t *value_str = value; + data = value_str->data; + data_len = value_str->len; + } + + apr_err = apr_memcache_set(cache->memcache, mc_key, data, data_len, 0, 0); + + /* ### Maybe write failures should be ignored (but logged)? */ + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, + _("Unknown memcached error while writing")); + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +svn_error_t * +memcache_iter(svn_boolean_t *completed, + void *cache_void, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't iterate a memcached cache.")); +} + +static svn_cache__vtable_t memcache_vtable = { + memcache_get, + memcache_set, + memcache_iter +}; + +svn_error_t * +svn_cache_create_memcache(svn_cache_t **cache_p, + svn_memcache_t *memcache, + svn_cache_serialize_func_t *serialize_func, + svn_cache_deserialize_func_t *deserialize_func, + apr_ssize_t klen, + const char *prefix, + apr_pool_t *pool) +{ + svn_cache_t *wrapper = apr_pcalloc(pool, sizeof(*wrapper)); + memcache_t *cache = apr_pcalloc(pool, sizeof(*cache)); + + cache->serialize_func = serialize_func; + cache->deserialize_func = deserialize_func; + cache->klen = klen; + cache->prefix = svn_path_uri_encode(prefix, pool); + cache->memcache = memcache->c; + + wrapper->vtable = &memcache_vtable; + wrapper->cache_internal = cache; + wrapper->error_handler = wrapper->error_baton = NULL; + + *cache_p = wrapper; + return SVN_NO_ERROR; +} + + +/*** Creating apr_memcache_t from svn_config_t. ***/ + +/* Baton for add_memcache_server. */ +struct ams_baton { + apr_memcache_t *memcache; + apr_pool_t *memcache_pool; + svn_error_t *err; +}; + +/* Implements svn_config_enumerator2_t. */ +static svn_boolean_t +add_memcache_server(const char *name, + const char *value, + void *baton, + apr_pool_t *pool) +{ + struct ams_baton *b = baton; + char *host, *scope; + apr_port_t port; + apr_status_t apr_err; + apr_memcache_server_t *server; + + apr_err = apr_parse_addr_port(&host, &scope, &port, + value, pool); + if (apr_err != APR_SUCCESS) + { + b->err = svn_error_wrap_apr(apr_err, + _("Error parsing memcache server '%s'"), + name); + return FALSE; + } + + if (scope) + { + b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL, + _("Scope not allowed in memcache server " + "'%s'"), + name); + return FALSE; + } + if (!host || !port) + { + b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL, + _("Must specify host and port for memcache " + "server '%s'"), + name); + return FALSE; + } + + /* Note: the four numbers here are only relevant when an + apr_memcache_t is being shared by multiple threads. */ + apr_err = apr_memcache_server_create(b->memcache_pool, + host, + port, + 0, /* min connections */ + 5, /* soft max connections */ + 10, /* hard max connections */ + 50, /* connection time to live (secs) */ + &server); + if (apr_err != APR_SUCCESS) + { + b->err = svn_error_wrap_apr(apr_err, + _("Unknown error creating memcache server")); + return FALSE; + } + + apr_err = apr_memcache_add_server(b->memcache, server); + if (apr_err != APR_SUCCESS) + { + b->err = svn_error_wrap_apr(apr_err, + _("Unknown error adding server to memcache")); + return FALSE; + } + + return TRUE; +} + +#else /* ! SVN_HAVE_MEMCACHE */ + +/* Stubs for no apr memcache library. */ + +struct svn_memcache_t { + void *unused; /* Let's not have a size-zero struct. */ +}; + +svn_error_t * +svn_cache_create_memcache(svn_cache_t **cache_p, + svn_memcache_t *memcache, + svn_cache_serialize_func_t *serialize_func, + svn_cache_deserialize_func_t *deserialize_func, + apr_ssize_t klen, + const char *prefix, + apr_pool_t *pool) +{ + return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL); +} + +#endif /* SVN_HAVE_MEMCACHE */ + +/* Implements svn_config_enumerator2_t. Just used for the + entry-counting return value of svn_config_enumerate2. */ +static svn_boolean_t +nop_enumerator(const char *name, + const char *value, + void *baton, + apr_pool_t *pool) +{ + return TRUE; +} + +svn_error_t * +svn_cache_make_memcache_from_config(svn_memcache_t **memcache_p, + svn_config_t *config, + apr_pool_t *pool) +{ + apr_uint16_t server_count; + apr_pool_t *subpool = svn_pool_create(pool); + + server_count = + svn_config_enumerate2(config, + SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS, + nop_enumerator, NULL, subpool); + + if (server_count == 0) + { + *memcache_p = NULL; + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } + +#if SVN_HAVE_MEMCACHE + { + struct ams_baton b; + svn_memcache_t *memcache = apr_pcalloc(pool, sizeof(*memcache)); + apr_status_t apr_err = apr_memcache_create(pool, + server_count, + 0, /* flags */ + &(memcache->c)); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, + _("Unknown error creating apr_memcache_t")); + + b.memcache = memcache->c; + b.memcache_pool = pool; + b.err = SVN_NO_ERROR; + svn_config_enumerate2(config, + SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS, + add_memcache_server, &b, + subpool); + + if (b.err) + return b.err; + + *memcache_p = memcache; + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } +#else /* ! SVN_HAVE_MEMCACHE */ + { + return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL); + } +#endif /* SVN_HAVE_MEMCACHE */ +}
diff --git a/subversion/libsvn_subr/cache.c b/subversion/libsvn_subr/cache.c new file mode 100644 index 0000000..3aed9e1 --- /dev/null +++ b/subversion/libsvn_subr/cache.c
@@ -0,0 +1,93 @@ +/* + * cache.c: cache interface for Subversion + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include "cache.h" + +svn_error_t * +svn_cache_set_error_handler(svn_cache_t *cache, + svn_cache_error_handler_t *handler, + void *baton, + apr_pool_t *pool) +{ + cache->error_handler = handler; + cache->error_baton = baton; + return SVN_NO_ERROR; +} + + +/* Give the error handler callback a chance to replace or ignore the + error. */ +static svn_error_t * +handle_error(svn_cache_t *cache, + svn_error_t *err, + apr_pool_t *pool) +{ + if (err && cache->error_handler) + err = (cache->error_handler)(err, cache->error_baton, pool); + return err; +} + + +svn_error_t * +svn_cache_get(void **value_p, + svn_boolean_t *found, + svn_cache_t *cache, + const void *key, + apr_pool_t *pool) +{ + /* In case any errors happen and are quelched, make sure we start + out with FOUND set to false. */ + *found = FALSE; + return handle_error(cache, + (cache->vtable->get)(value_p, + found, + cache->cache_internal, + key, + pool), + pool); +} + +svn_error_t * +svn_cache_set(svn_cache_t *cache, + const void *key, + void *value, + apr_pool_t *pool) +{ + return handle_error(cache, + (cache->vtable->set)(cache->cache_internal, + key, + value, + pool), + pool); +} + + +svn_error_t * +svn_cache_iter(svn_boolean_t *completed, + svn_cache_t *cache, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *pool) +{ + return (cache->vtable->iter)(completed, + cache->cache_internal, + user_cb, + user_baton, + pool); +} +
diff --git a/subversion/libsvn_subr/cache.h b/subversion/libsvn_subr/cache.h new file mode 100644 index 0000000..b39b236 --- /dev/null +++ b/subversion/libsvn_subr/cache.h
@@ -0,0 +1,59 @@ +/* + * cache.h: cache vtable interface + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_CACHE_H +#define SVN_LIBSVN_SUBR_CACHE_H + +#include "svn_cache.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct { + svn_error_t *(*get)(void **value, + svn_boolean_t *found, + void *cache_implementation, + const void *key, + apr_pool_t *pool); + + svn_error_t *(*set)(void *cache_implementation, + const void *key, + void *value, + apr_pool_t *pool); + + svn_error_t *(*iter)(svn_boolean_t *completed, + void *cache_implementation, + svn_iter_apr_hash_cb_t func, + void *baton, + apr_pool_t *pool); +} svn_cache__vtable_t; + +struct svn_cache_t { + const svn_cache__vtable_t *vtable; + svn_cache_error_handler_t *error_handler; + void *error_baton; + void *cache_internal; +}; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_CACHE_H */
diff --git a/subversion/libsvn_subr/stream.c b/subversion/libsvn_subr/stream.c index 532c41e..5680b7a 100644 --- a/subversion/libsvn_subr/stream.c +++ b/subversion/libsvn_subr/stream.c
@@ -820,13 +820,59 @@ /* Miscellaneous stream functions. */ -struct string_stream_baton +struct stringbuf_stream_baton { svn_stringbuf_t *str; apr_size_t amt_read; }; static svn_error_t * +read_handler_stringbuf(void *baton, char *buffer, apr_size_t *len) +{ + struct stringbuf_stream_baton *btn = baton; + apr_size_t left_to_read = btn->str->len - btn->amt_read; + + *len = (*len > left_to_read) ? left_to_read : *len; + memcpy(buffer, btn->str->data + btn->amt_read, *len); + btn->amt_read += *len; + return SVN_NO_ERROR; +} + +static svn_error_t * +write_handler_stringbuf(void *baton, const char *data, apr_size_t *len) +{ + struct stringbuf_stream_baton *btn = baton; + + svn_stringbuf_appendbytes(btn->str, data, *len); + return SVN_NO_ERROR; +} + +svn_stream_t * +svn_stream_from_stringbuf(svn_stringbuf_t *str, + apr_pool_t *pool) +{ + svn_stream_t *stream; + struct stringbuf_stream_baton *baton; + + if (! str) + return svn_stream_empty(pool); + + baton = apr_palloc(pool, sizeof(*baton)); + baton->str = str; + baton->amt_read = 0; + stream = svn_stream_create(baton, pool); + svn_stream_set_read(stream, read_handler_stringbuf); + svn_stream_set_write(stream, write_handler_stringbuf); + return stream; +} + +struct string_stream_baton +{ + svn_string_t *str; + apr_size_t amt_read; +}; + +static svn_error_t * read_handler_string(void *baton, char *buffer, apr_size_t *len) { struct string_stream_baton *btn = baton; @@ -838,18 +884,9 @@ return SVN_NO_ERROR; } -static svn_error_t * -write_handler_string(void *baton, const char *data, apr_size_t *len) -{ - struct string_stream_baton *btn = baton; - - svn_stringbuf_appendbytes(btn->str, data, *len); - return SVN_NO_ERROR; -} - svn_stream_t * -svn_stream_from_stringbuf(svn_stringbuf_t *str, - apr_pool_t *pool) +svn_stream_from_string(svn_string_t *str, + apr_pool_t *pool) { svn_stream_t *stream; struct string_stream_baton *baton; @@ -862,7 +899,6 @@ baton->amt_read = 0; stream = svn_stream_create(baton, pool); svn_stream_set_read(stream, read_handler_string); - svn_stream_set_write(stream, write_handler_string); return stream; }
diff --git a/subversion/libsvn_subr/svn_base64.c b/subversion/libsvn_subr/svn_base64.c index 3886221..45d49b1 100644 --- a/subversion/libsvn_subr/svn_base64.c +++ b/subversion/libsvn_subr/svn_base64.c
@@ -66,10 +66,11 @@ data from call to call, and *LINELEN carries the length of the current output line. Make INBUF have room for three characters and initialize *INBUFLEN and *LINELEN to 0. Output will be appended to - STR. */ + STR. Include newlines every so often if BREAK_LINES is true. */ static void encode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len, - unsigned char *inbuf, int *inbuflen, int *linelen) + unsigned char *inbuf, int *inbuflen, int *linelen, + svn_boolean_t break_lines) { char group[4]; const char *p = data, *end = data + len; @@ -83,7 +84,7 @@ svn_stringbuf_appendbytes(str, group, 4); *inbuflen = 0; *linelen += 4; - if (*linelen == BASE64_LINELEN) + if (break_lines && *linelen == BASE64_LINELEN) { svn_stringbuf_appendcstr(str, "\n"); *linelen = 0; @@ -96,11 +97,12 @@ } -/* Encode leftover data, if any, and possibly a final newline, - appending to STR. LEN must be in the range 0..2. */ +/* Encode leftover data, if any, and possibly a final newline (if + there has been any data and BREAK_LINES is set), appending to STR. + LEN must be in the range 0..2. */ static void encode_partial_group(svn_stringbuf_t *str, const unsigned char *extra, - int len, int linelen) + int len, int linelen, svn_boolean_t break_lines) { unsigned char ingroup[3]; char outgroup[4]; @@ -114,7 +116,7 @@ svn_stringbuf_appendbytes(str, outgroup, 4); linelen += 4; } - if (linelen > 0) + if (break_lines && linelen > 0) svn_stringbuf_appendcstr(str, "\n"); } @@ -130,7 +132,7 @@ svn_error_t *err = SVN_NO_ERROR; /* Encode this block of data and write it out. */ - encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen); + encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE); enclen = encoded->len; if (enclen != 0) err = svn_stream_write(eb->output, encoded->data, &enclen); @@ -149,7 +151,7 @@ svn_error_t *err = SVN_NO_ERROR; /* Encode a partial group at the end if necessary, and write it out. */ - encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen); + encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE); enclen = encoded->len; if (enclen != 0) err = svn_stream_write(eb->output, encoded->data, &enclen); @@ -181,20 +183,30 @@ const svn_string_t * -svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool) +svn_base64_encode_string2(const svn_string_t *str, + svn_boolean_t break_lines, + apr_pool_t *pool) { svn_stringbuf_t *encoded = svn_stringbuf_create("", pool); svn_string_t *retval = apr_pcalloc(pool, sizeof(*retval)); unsigned char ingroup[3]; int ingrouplen = 0, linelen = 0; - encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen); - encode_partial_group(encoded, ingroup, ingrouplen, linelen); + encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen, + break_lines); + encode_partial_group(encoded, ingroup, ingrouplen, linelen, + break_lines); retval->data = encoded->data; retval->len = encoded->len; return retval; } +const svn_string_t * +svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool) +{ + return svn_base64_encode_string2(str, TRUE, pool); +} + /* Base64-encoded input --> binary output */ @@ -373,8 +385,8 @@ * does an implicit unsigned char * cast. */ encode_bytes(md5str, (char*)digest, APR_MD5_DIGESTSIZE, ingroup, - &ingrouplen, &linelen); - encode_partial_group(md5str, ingroup, ingrouplen, linelen); + &ingrouplen, &linelen, TRUE); + encode_partial_group(md5str, ingroup, ingrouplen, linelen, TRUE); /* Our base64-encoding routines append a final newline if any data was created at all, so let's hack that off. */
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c index d812326..69dc6ad 100644 --- a/subversion/svnserve/serve.c +++ b/subversion/svnserve/serve.c
@@ -2889,6 +2889,7 @@ return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, "No access allowed to this repository", b, conn, pool); + return SVN_NO_ERROR; }
diff --git a/subversion/tests/cmdline/svndumpfilter_tests.py b/subversion/tests/cmdline/svndumpfilter_tests.py index 3893355..0d394fa 100755 --- a/subversion/tests/cmdline/svndumpfilter_tests.py +++ b/subversion/tests/cmdline/svndumpfilter_tests.py
@@ -72,7 +72,8 @@ "--skip-missing-merge-sources", "--drop-empty-revs", "--renumber-revs", "--quiet") - load_and_verify_dumpstream(sbox, [], [], None, filtered_out) + load_and_verify_dumpstream(sbox, [], [], None, filtered_out, + "--ignore-uuid") # Verify the svn:mergeinfo properties svntest.actions.run_and_verify_svn(None, @@ -91,7 +92,8 @@ "--skip-missing-merge-sources", "--drop-empty-revs", "--renumber-revs", "--quiet") - load_and_verify_dumpstream(sbox, [], [], None, filtered_out) + load_and_verify_dumpstream(sbox, [], [], None, filtered_out, + "--ignore-uuid") # Verify the svn:mergeinfo properties svntest.actions.run_and_verify_svn(None,
diff --git a/subversion/tests/cmdline/svntest/main.py b/subversion/tests/cmdline/svntest/main.py index da28863..17e845f 100644 --- a/subversion/tests/cmdline/svntest/main.py +++ b/subversion/tests/cmdline/svntest/main.py
@@ -153,6 +153,9 @@ # ('neon', 'serf') http_library = None +# Configuration file (copied into FSFS fsfs.conf). +config_file = None + # Global variable indicating what the minor version of the server # tested against is (4 for 1.4.x, for example). server_minor_version = 5 @@ -304,6 +307,11 @@ return os.path.join(repo_dir, "conf", "svnserve.conf") +def get_fsfs_conf_file_path(repo_dir): + "Return the path of the fsfs.conf file in REPO_DIR." + + return os.path.join(repo_dir, "db", "fsfs.conf") + # Run any binary, logging the command line and return code def run_command(command, error_expected, binary_mode=0, *varargs): """Run COMMAND with VARARGS; return exit code as int; stdout, stderr @@ -625,6 +633,10 @@ file_append(get_svnserve_conf_file_path(path), "password-db = passwd\n") file_append(os.path.join(path, "conf", "passwd"), "[users]\njrandom = rayjandom\njconstant = rayjandom\n"); + + if config_file is not None and (fs_type is None or fs_type == 'fsfs'): + shutil.copy(config_file, get_fsfs_conf_file_path(path)) + # make the repos world-writeable, for mod_dav_svn's sake. chmod_tree(path, 0666, 0666) @@ -1241,6 +1253,7 @@ " useful during test development!" print " --server-minor-version Set the minor version for the server.\n" \ " Supports version 4 or 5." + print " --config-file Configuration file for tests." print " --help This information" @@ -1270,6 +1283,7 @@ global svnversion_binary global command_line_parsed global http_library + global config_file global server_minor_version testnums = [] @@ -1279,13 +1293,14 @@ parallel = 0 svn_bin = None use_jsvn = False + config_file = None try: opts, args = my_getopt(sys.argv[1:], 'vqhpc', ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup', 'list', 'enable-sasl', 'help', 'parallel', - 'bin=', 'http-library=', 'server-minor-version=', - 'use-jsvn', 'development']) + 'bin=', 'http-library=', 'server-minor-version=', + 'use-jsvn', 'development', 'config-file=']) except getopt.GetoptError, e: print "ERROR: %s\n" % e usage() @@ -1355,6 +1370,9 @@ elif opt == '--development': setup_development_mode() + elif opt == '--config-file': + config_file = val + if test_area_url[-1:] == '/': # Normalize url to have no trailing slash test_area_url = test_area_url[:-1]
diff --git a/subversion/tests/libsvn_fs/fs-test.c b/subversion/tests/libsvn_fs/fs-test.c index 23aa5a2..8859830 100644 --- a/subversion/tests/libsvn_fs/fs-test.c +++ b/subversion/tests/libsvn_fs/fs-test.c
@@ -133,7 +133,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-trivial-txn", - opts->fs_type, pool)); + opts, pool)); /* Begin a new transaction that is based on revision 0. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); @@ -189,7 +189,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-reopen-trivial-txn", - opts->fs_type, pool)); + opts, pool)); /* Begin a new transaction that is based on revision 0. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); @@ -228,7 +228,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-file-txn", - opts->fs_type, pool)); + opts, pool)); /* Begin a new transaction that is based on revision 0. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); @@ -262,7 +262,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-txn-list", - opts->fs_type, pool)); + opts, pool)); /* Begin a new transaction, get its name (in the top pool), close it. */ subpool = svn_pool_create(pool); @@ -390,7 +390,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-txn-names-are-not-reused", - opts->fs_type, pool)); + opts, pool)); subpool = svn_pool_create(pool); @@ -431,7 +431,7 @@ wstring = svn_stringbuf_create("Wicki wild, wicki wicki wild.", pool); SVN_ERR(svn_test__create_fs(&fs, "test-repo-read-and-write-file", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -473,7 +473,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-mini-tree-txn", - opts->fs_type, pool)); + opts, pool)); /* Begin a new transaction that is based on revision 0. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); @@ -512,7 +512,7 @@ /* Prepare a txn to receive the greek tree. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-greek-tree-txn", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -577,7 +577,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-list-dir", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -659,7 +659,7 @@ /* Open the fs */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-rev-props", - opts->fs_type, pool)); + opts, pool)); /* Set some properties on the revision. */ for (i = 0; i < 4; i++) @@ -762,7 +762,7 @@ /* Open the fs */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-txn-props", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); /* Set some properties on the revision. */ @@ -915,7 +915,7 @@ /* Open the fs and transaction */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-props", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -1071,7 +1071,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_fs(&fs, "test-repo-youngest-rev", - opts->fs_type, pool)); + opts, pool)); /* Get youngest revision of brand spankin' new filesystem. */ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); @@ -1122,7 +1122,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-commit", - opts->fs_type, pool)); + opts, pool)); /* Save the current youngest revision. */ SVN_ERR(svn_fs_youngest_rev(&before_rev, fs, pool)); @@ -1181,7 +1181,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-validate-tree-entries", - opts->fs_type, pool)); + opts, pool)); /* In a txn, create the greek tree. */ subpool = svn_pool_create(pool); @@ -1314,7 +1314,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-merging-commit", - opts->fs_type, pool)); + opts, pool)); /* Initialize our revision number stuffs. */ for (i = 0; @@ -2095,7 +2095,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-copy-test", - opts->fs_type, pool)); + opts, pool)); /* In first txn, create and commit the greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); @@ -2398,7 +2398,7 @@ /* Prepare a txn to receive the greek tree. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-from-dir", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -2554,7 +2554,7 @@ /* Prepare a txn to receive the greek tree. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-tree", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -2910,7 +2910,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-commit-date", - opts->fs_type, pool)); + opts, pool)); before_commit = apr_time_now(); @@ -2966,7 +2966,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-old-revisions", - opts->fs_type, pool)); + opts, pool)); /* Commit a greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); @@ -3332,7 +3332,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-all-revisions", - opts->fs_type, pool)); + opts, pool)); /***********************************************************************/ /* REVISION 0 */ @@ -3650,7 +3650,7 @@ static svn_error_t * file_integrity_helper(apr_size_t filesize, apr_uint32_t *seed, - const char *fs_type, const char *fs_name, + svn_test_opts_t *opts, const char *fs_name, apr_pool_t *pool) { svn_fs_t *fs; @@ -3667,7 +3667,7 @@ svn_revnum_t j; /* Create a filesystem and repository. */ - SVN_ERR(svn_test__create_fs(&fs, fs_name, fs_type, pool)); + SVN_ERR(svn_test__create_fs(&fs, fs_name, opts, pool)); /* Set up our file contents string buffer. */ content_buffer = apr_palloc(pool, filesize); @@ -3799,7 +3799,7 @@ /* Being no larger than the standard delta window size affects deltification internally, so test that. */ - return file_integrity_helper(SVN_DELTA_WINDOW_SIZE, &seed, opts->fs_type, + return file_integrity_helper(SVN_DELTA_WINDOW_SIZE, &seed, opts, "test-repo-medium-file-integrity", pool); } @@ -3820,7 +3820,7 @@ /* Being larger than the standard delta window size affects deltification internally, so test that. */ - return file_integrity_helper(SVN_DELTA_WINDOW_SIZE + 1, &seed, opts->fs_type, + return file_integrity_helper(SVN_DELTA_WINDOW_SIZE + 1, &seed, opts, "test-repo-large-file-integrity", pool); } @@ -3845,7 +3845,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-root-revision", - opts->fs_type, pool)); + opts, pool)); /* Create and commit the greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); @@ -3971,7 +3971,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-created-rev", - opts->fs_type, pool)); + opts, pool)); /* Created the greek tree in revision 1. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); @@ -4086,7 +4086,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-related", - opts->fs_type, pool)); + opts, pool)); /*** Step I: Build up some state in our repository through a series of commits */ @@ -4296,7 +4296,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-branch-test", - opts->fs_type, pool)); + opts, pool)); /*** Revision 1: Create the greek tree in revision. ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); @@ -4382,7 +4382,7 @@ apr_md5(expected_digest, str->data, str->len); SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-checksum", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_fs_make_file(txn_root, "fact", pool)); @@ -4474,7 +4474,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-closest-copy", - opts->fs_type, pool)); + opts, pool)); /* In first txn, create and commit the greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool)); @@ -4575,7 +4575,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-root-revisions", - opts->fs_type, pool)); + opts, pool)); /* In first txn, create and commit the greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool)); @@ -4651,7 +4651,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-unordered-txn-dirprops", - opts->fs_type, pool)); + opts, pool)); /* Create and commit the greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); @@ -4727,7 +4727,7 @@ /* Prepare a filesystem. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-set-uuid", - opts->fs_type, pool)); + opts, pool)); /* Set the repository UUID to something fixed. */ SVN_ERR(svn_fs_set_uuid(fs, fixed_uuid, pool)); @@ -4778,7 +4778,7 @@ /* Create the repository. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-origin-rev", - opts->fs_type, pool)); + opts, pool)); /* Revision 1: Create the Greek tree. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
diff --git a/subversion/tests/libsvn_fs/locks-test.c b/subversion/tests/libsvn_fs/locks-test.c index 5965224..7e0007c 100644 --- a/subversion/tests/libsvn_fs/locks-test.c +++ b/subversion/tests/libsvn_fs/locks-test.c
@@ -112,7 +112,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-only", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -158,7 +158,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lookup-lock-by-path", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -208,7 +208,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-attach-lock", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -267,7 +267,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-get-locks", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -402,7 +402,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-lock", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -460,7 +460,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-credentials", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -557,7 +557,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-final-lock-check", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -621,7 +621,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-dir-propchange", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -678,7 +678,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-name-reservation", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -737,7 +737,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-directory-locks-kinda", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -815,7 +815,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-expiration", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -906,7 +906,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-steal-refresh", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -993,7 +993,7 @@ /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-out-of-date", - opts->fs_type, pool)); + opts, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
diff --git a/subversion/tests/libsvn_fs_base/changes-test.c b/subversion/tests/libsvn_fs_base/changes-test.c index 5d34ddf..e77c437 100644 --- a/subversion/tests/libsvn_fs_base/changes-test.c +++ b/subversion/tests/libsvn_fs_base/changes-test.c
@@ -173,8 +173,8 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-add", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-add", + pool)); /* Add the standard slew of changes. */ SVN_ERR(add_standard_changes(fs, pool)); @@ -201,8 +201,8 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-fetch", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-fetch", + pool)); /* First, verify that we can request changes for an arbitrary key without error. */ @@ -309,8 +309,8 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-delete", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-delete", + pool)); /* Add the standard slew of changes. */ SVN_ERR(add_standard_changes(fs, pool)); @@ -505,8 +505,8 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-fetch", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-fetch", + pool)); /* First, verify that we can request changes for an arbitrary key without error. */ @@ -575,9 +575,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-changes-fetch-ordering", - "bdb", pool)); + pool)); /*** REVISION 1: Make some files and dirs. ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
diff --git a/subversion/tests/libsvn_fs_base/fs-base-test.c b/subversion/tests/libsvn_fs_base/fs-base-test.c index 5a362a0..340466f 100644 --- a/subversion/tests/libsvn_fs_base/fs-base-test.c +++ b/subversion/tests/libsvn_fs_base/fs-base-test.c
@@ -58,8 +58,8 @@ return SVN_NO_ERROR; /* Create and close a repository. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-berkeley", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-create-berkeley", + pool)); return SVN_NO_ERROR; } @@ -88,8 +88,8 @@ return SVN_NO_ERROR; /* Create and close a repository (using fs). */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-open-berkeley", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-open-berkeley", + pool)); /* Create a different fs object, and use it to re-open the repository again. */ @@ -278,8 +278,8 @@ return SVN_NO_ERROR; /* Prepare two txns to receive the Greek tree. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-abort-txn", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-abort-txn", + pool)); SVN_ERR(svn_fs_begin_txn(&txn1, fs, 0, pool)); SVN_ERR(svn_fs_begin_txn(&txn2, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn1_root, txn1, pool)); @@ -517,8 +517,8 @@ return SVN_NO_ERROR; /* Prepare a txn to receive the greek tree. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-from-dir", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-del-from-dir", + pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -713,8 +713,8 @@ */ /* Prepare a txn to receive the greek tree. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-tree", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-del-tree", + pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); @@ -1200,8 +1200,8 @@ return SVN_NO_ERROR; /* Create a filesystem and repository. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-within-copy", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-create-within-copy", + pool)); /*** Revision 1: Create the greek tree in revision. ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); @@ -1329,8 +1329,8 @@ return SVN_NO_ERROR; /* Create a filesystem and repository. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-skip-deltas", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-skip-deltas", + pool)); /* Create the file. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); @@ -1408,8 +1408,8 @@ return SVN_NO_ERROR; /* Create a filesystem and repository. */ - SVN_ERR(svn_test__create_fs(&fs, "test-repo-redundant-copy", - "bdb", pool)); + SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-redundant-copy", + pool)); /* Create the greek tree in revision 1. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
diff --git a/subversion/tests/libsvn_fs_base/strings-reps-test.c b/subversion/tests/libsvn_fs_base/strings-reps-test.c index 7dc521a..cedd22c 100644 --- a/subversion/tests/libsvn_fs_base/strings-reps-test.c +++ b/subversion/tests/libsvn_fs_base/strings-reps-test.c
@@ -103,9 +103,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-write-new-rep", - "bdb", pool)); + pool)); /* Set up transaction baton */ args.fs = fs; @@ -142,9 +142,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-write-rep", - "bdb", pool)); + pool)); /* Set up transaction baton */ new_args.fs = fs; @@ -220,9 +220,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-read-rep", - "bdb", pool)); + pool)); /* Set up transaction baton */ new_args.fs = fs; @@ -305,9 +305,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-delete-rep", - "bdb", pool)); + pool)); /* Set up transaction baton */ new_args.fs = fs; @@ -540,9 +540,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-test-strings", - "bdb", pool)); + pool)); /* The plan (after each step below, verify the size and contents of the string): @@ -645,9 +645,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-test-strings", - "bdb", pool)); + pool)); args.fs = fs; args.key = NULL; @@ -675,9 +675,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-abort-string", - "bdb", pool)); + pool)); /* The plan: @@ -743,9 +743,9 @@ return SVN_NO_ERROR; /* Create a new fs and repos */ - SVN_ERR(svn_test__create_fs + SVN_ERR(svn_test__create_bdb_fs (&fs, "test-repo-copy-string", - "bdb", pool)); + pool)); /* Write a new string (string1). */ args.fs = fs;
diff --git a/subversion/tests/libsvn_ra_local/ra-local-test.c b/subversion/tests/libsvn_ra_local/ra-local-test.c index 5714582..ecff146 100644 --- a/subversion/tests/libsvn_ra_local/ra-local-test.c +++ b/subversion/tests/libsvn_ra_local/ra-local-test.c
@@ -85,7 +85,7 @@ static svn_error_t * make_and_open_local_repos(svn_ra_session_t **session, const char *repos_name, - const char *fs_type, + svn_test_opts_t *opts, apr_pool_t *pool) { svn_repos_t *repos; @@ -94,7 +94,7 @@ SVN_ERR(svn_ra_create_callbacks(&cbtable, pool)); - SVN_ERR(svn_test__create_repos(&repos, repos_name, fs_type, pool)); + SVN_ERR(svn_test__create_repos(&repos, repos_name, opts, pool)); SVN_ERR(svn_ra_initialize(pool)); SVN_ERR(current_directory_url(&url, repos_name, pool)); @@ -130,7 +130,7 @@ return SVN_NO_ERROR; SVN_ERR(make_and_open_local_repos(&session, - "test-repo-open", opts->fs_type, pool)); + "test-repo-open", opts, pool)); return SVN_NO_ERROR; } @@ -152,7 +152,7 @@ return SVN_NO_ERROR; SVN_ERR(make_and_open_local_repos(&session, - "test-repo-getrev", opts->fs_type, + "test-repo-getrev", opts, pool)); /* Get the youngest revision and make sure it's 0. */ @@ -294,14 +294,14 @@ static svn_error_t * check_split_url(const char *repos_path, const char *in_repos_path, - const char *fs_type, + svn_test_opts_t *opts, apr_pool_t *pool) { svn_repos_t *repos; const char *url, *root_url, *repos_part, *in_repos_part; /* Create a filesystem and repository */ - SVN_ERR(svn_test__create_repos(&repos, repos_path, fs_type, pool)); + SVN_ERR(svn_test__create_repos(&repos, repos_path, opts, pool)); SVN_ERR(current_directory_url(&root_url, repos_path, pool)); if (in_repos_path) @@ -347,15 +347,15 @@ in-repository path begins. */ SVN_ERR(check_split_url("test-repo-split-fs1", "/trunk/foobar/quux.c", - opts->fs_type, + opts, pool)); SVN_ERR(check_split_url("test-repo-split-fs2", "/alpha/beta/gamma/delta/epsilon/zeta/eta/theta", - opts->fs_type, + opts, pool)); SVN_ERR(check_split_url("test-repo-split-fs3", NULL, - opts->fs_type, + opts, pool)); return SVN_NO_ERROR;
diff --git a/subversion/tests/libsvn_repos/repos-test.c b/subversion/tests/libsvn_repos/repos-test.c index 6bbd7e3..d715959 100644 --- a/subversion/tests/libsvn_repos/repos-test.c +++ b/subversion/tests/libsvn_repos/repos-test.c
@@ -73,7 +73,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-dir-deltas", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); expected_trees[revision_count].num_entries = 0; expected_trees[revision_count++].entries = 0; @@ -380,7 +380,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-del-under-copy", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); /* Prepare a txn to receive the greek tree. */ @@ -520,7 +520,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-revisions-changed", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); /*** Testing Algorithm *** @@ -779,7 +779,7 @@ /* Create the repository with a Greek tree. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); @@ -832,7 +832,7 @@ /* Create the repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations2", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); /* Revision 1: Add a directory /foo */ @@ -1045,7 +1045,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-rmlocks", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); /* Prepare a txn to receive the greek tree. */ @@ -1419,7 +1419,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-authz", - opts->fs_type, subpool)); + opts, subpool)); fs = svn_repos_fs(repos); /* Prepare a txn to receive the greek tree. */ @@ -1637,7 +1637,7 @@ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-continue", - opts->fs_type, subpool)); + opts, subpool)); fs = svn_repos_fs(repos); /* Prepare a txn to receive the greek tree. */ @@ -1818,7 +1818,7 @@ /* Create the repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-location-segments", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); /* Revision 1: Create the Greek tree. */ @@ -2011,7 +2011,7 @@ return SVN_NO_ERROR; SVN_ERR(svn_test__create_repos(&repos, "test-repo-reporter-depth-exclude", - opts->fs_type, pool)); + opts, pool)); fs = svn_repos_fs(repos); /* Prepare a txn to receive the greek tree. */
diff --git a/subversion/tests/libsvn_subr/cache-test.c b/subversion/tests/libsvn_subr/cache-test.c new file mode 100644 index 0000000..4828ace --- /dev/null +++ b/subversion/tests/libsvn_subr/cache-test.c
@@ -0,0 +1,274 @@ +/* + * cache-test.c -- test the in-memory cache + * + * ==================================================================== + * Copyright (c) 2006 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include <stdio.h> +#include <string.h> +#include <apr_general.h> +#include <apr_lib.h> +#include <apr_time.h> + +#include "svn_cache.h" +#include "svn_pools.h" + +#include "svn_private_config.h" + +#include "../svn_test.h" + +static svn_cache_dup_func_t dup_revnum; +static svn_error_t * +dup_revnum(void **out, + void *in, + apr_pool_t *pool) +{ + svn_revnum_t *in_rn = in, *duped = apr_palloc(pool, sizeof(*duped)); + + *duped = *in_rn; + + *out = duped; + + return SVN_NO_ERROR; +} + +static svn_cache_serialize_func_t serialize_revnum; +static svn_error_t * +serialize_revnum(char **data, + apr_size_t *data_len, + void *in, + apr_pool_t *pool) +{ + *data_len = sizeof(svn_revnum_t); + *data = apr_pmemdup(pool, in, *data_len); + + return SVN_NO_ERROR; +} + + +static svn_cache_deserialize_func_t deserialize_revnum; +static svn_error_t * +deserialize_revnum(void **out, + const char *data, + apr_size_t data_len, + apr_pool_t *pool) +{ + svn_revnum_t *in_rev, *out_rev; + if (data_len != sizeof(*in_rev)) + return svn_error_create(SVN_ERR_REVNUM_PARSE_FAILURE, NULL, + _("Bad size for revision number in cache")); + in_rev = (svn_revnum_t *) data; + out_rev = apr_palloc(pool, sizeof(*out_rev)); + *out_rev = *in_rev; + *out = out_rev; + return SVN_NO_ERROR; +} + +static svn_error_t * +basic_cache_test(svn_cache_t *cache, + svn_boolean_t size_is_one, + apr_pool_t *pool) +{ + svn_boolean_t found; + svn_revnum_t twenty = 20, thirty = 30, *answer; + apr_pool_t *subpool; + + /* We use a subpool for all calls in this test and aggressively + * clear it, to try to find any bugs where the cached values aren't + * actually saved away in the cache's pools. */ + subpool = svn_pool_create(pool); + + SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "twenty", subpool)); + if (found) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "cache found an entry that wasn't there"); + svn_pool_clear(subpool); + + SVN_ERR(svn_cache_set(cache, "twenty", &twenty, subpool)); + svn_pool_clear(subpool); + + SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "twenty", subpool)); + if (! found) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "cache failed to find entry for 'twenty'"); + if (*answer != 20) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected 20 but found '%ld'", *answer); + svn_pool_clear(subpool); + + SVN_ERR(svn_cache_set(cache, "thirty", &thirty, subpool)); + svn_pool_clear(subpool); + + SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "thirty", subpool)); + if (! found) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "cache failed to find entry for 'thirty'"); + if (*answer != 30) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected 30 but found '%ld'", *answer); + + if (size_is_one) + { + SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "twenty", subpool)); + if (found) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "cache found entry for 'twenty' that should have " + "expired"); + } + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_inprocess_cache_basic(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_cache_t *cache; + + *msg = "basic inprocess svn_cache test"; + + if (msg_only) + return SVN_NO_ERROR; + + /* Create a cache with just one entry. */ + SVN_ERR(svn_cache_create_inprocess(&cache, + dup_revnum, + APR_HASH_KEY_STRING, + 1, + 1, + TRUE, + pool)); + + return basic_cache_test(cache, TRUE, pool); +} + +static svn_error_t * +test_memcache_basic(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_cache_t *cache; + svn_config_t *config; + svn_memcache_t *memcache = NULL; + const char *prefix = apr_psprintf(pool, + "test_memcache_basic-%" APR_TIME_T_FMT, + apr_time_now()); + + *msg = "basic memcache svn_cache test"; + + if (msg_only) + return SVN_NO_ERROR; + + if (opts->config_file) + { + SVN_ERR(svn_config_read(&config, opts->config_file, TRUE, pool)); + SVN_ERR(svn_cache_make_memcache_from_config(&memcache, config, pool)); + } + + if (! memcache) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "not configured to use memcached"); + + + /* Create a memcache-based cache. */ + SVN_ERR(svn_cache_create_memcache(&cache, + memcache, + serialize_revnum, + deserialize_revnum, + APR_HASH_KEY_STRING, + prefix, + pool)); + + return basic_cache_test(cache, FALSE, pool); +} + + + +static svn_error_t * +test_memcache_long_key(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_cache_t *cache; + svn_config_t *config; + svn_memcache_t *memcache = NULL; + svn_revnum_t fifty = 50, *answer; + svn_boolean_t found = FALSE; + const char *prefix = apr_psprintf(pool, + "test_memcache_long_key-%" APR_TIME_T_FMT, + apr_time_now()); + static const char *long_key = + "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 50 */ + "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 100 */ + "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 150 */ + "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 200 */ + "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 250 */ + "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 300 */ + ; + + *msg = "memcache svn_cache with very long keys"; + + if (msg_only) + return SVN_NO_ERROR; + + if (opts->config_file) + { + SVN_ERR(svn_config_read(&config, opts->config_file, TRUE, pool)); + SVN_ERR(svn_cache_make_memcache_from_config(&memcache, config, pool)); + } + + if (! memcache) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "not configured to use memcached"); + + + /* Create a memcache-based cache. */ + SVN_ERR(svn_cache_create_memcache(&cache, + memcache, + serialize_revnum, + deserialize_revnum, + APR_HASH_KEY_STRING, + prefix, + pool)); + + SVN_ERR(svn_cache_set(cache, long_key, &fifty, pool)); + SVN_ERR(svn_cache_get((void **) &answer, &found, cache, long_key, pool)); + + if (! found) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "cache failed to find entry for 'fifty'"); + if (*answer != 50) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected 50 but found '%ld'", *answer); + + return SVN_NO_ERROR; +} + + +/* The test table. */ + +struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_PASS(test_inprocess_cache_basic), + SVN_TEST_PASS(test_memcache_basic), + SVN_TEST_PASS(test_memcache_long_key), + SVN_TEST_NULL + };
diff --git a/subversion/tests/svn_test.h b/subversion/tests/svn_test.h index 2860e80..a7756bc 100644 --- a/subversion/tests/svn_test.h +++ b/subversion/tests/svn_test.h
@@ -37,6 +37,8 @@ { /* Description of the fs backend that should be used for testing. */ const char *fs_type; + /* Config file. */ + const char *config_file; /* Add future "arguments" here. */ } svn_test_opts_t;
diff --git a/subversion/tests/svn_test_fs.c b/subversion/tests/svn_test_fs.c index c2daad3..3b29017 100644 --- a/subversion/tests/svn_test_fs.c +++ b/subversion/tests/svn_test_fs.c
@@ -73,11 +73,11 @@ } -svn_error_t * -svn_test__create_fs(svn_fs_t **fs_p, - const char *name, - const char *fs_type, - apr_pool_t *pool) +static svn_error_t * +create_fs(svn_fs_t **fs_p, + const char *name, + const char *fs_type, + apr_pool_t *pool) { apr_finfo_t finfo; apr_hash_t *fs_config = make_fs_config(fs_type, pool); @@ -110,15 +110,63 @@ return SVN_NO_ERROR; } +static svn_error_t * +maybe_install_fsfs_conf(svn_fs_t *fs, + svn_test_opts_t *opts, + svn_boolean_t *must_reopen, + apr_pool_t *pool) +{ + *must_reopen = FALSE; + if (strcmp(opts->fs_type, "fsfs") != 0 || ! opts->config_file) + return SVN_NO_ERROR; + + *must_reopen = TRUE; + return svn_io_copy_file(opts->config_file, + svn_path_join(svn_fs_path(fs, pool), "fsfs.conf", pool), + FALSE, + pool); +} + + +svn_error_t * +svn_test__create_bdb_fs(svn_fs_t **fs_p, + const char *name, + apr_pool_t *pool) +{ + return create_fs(fs_p, name, "bdb", pool); +} + +svn_error_t * +svn_test__create_fs(svn_fs_t **fs_p, + const char *name, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_boolean_t must_reopen; + + SVN_ERR(create_fs(fs_p, name, opts->fs_type, pool)); + + SVN_ERR(maybe_install_fsfs_conf(*fs_p, opts, &must_reopen, pool)); + if (must_reopen) + { + SVN_ERR(svn_fs_open(fs_p, name, NULL, pool)); + svn_fs_set_warning_func(*fs_p, fs_warning_handler, NULL); + } + + return SVN_NO_ERROR; +} + svn_error_t * svn_test__create_repos(svn_repos_t **repos_p, const char *name, - const char *fs_type, + svn_test_opts_t *opts, apr_pool_t *pool) { apr_finfo_t finfo; - apr_hash_t *fs_config = make_fs_config(fs_type, pool); + svn_repos_t *repos; + svn_boolean_t must_reopen; + apr_hash_t *fs_config = make_fs_config(opts->fs_type, pool); /* If there's already a repository named NAME, delete it. Doing things this way means that repositories stick around after a @@ -134,12 +182,21 @@ "there is already a file named '%s'", name); } - SVN_ERR(svn_repos_create(repos_p, name, NULL, NULL, NULL, + SVN_ERR(svn_repos_create(&repos, name, NULL, NULL, NULL, fs_config, pool)); /* Register this repo for cleanup. */ svn_test_add_dir_cleanup(name); + SVN_ERR(maybe_install_fsfs_conf(svn_repos_fs(repos), opts, &must_reopen, + pool)); + if (must_reopen) + { + SVN_ERR(svn_repos_open(&repos, name, pool)); + svn_fs_set_warning_func(svn_repos_fs(repos), fs_warning_handler, NULL); + } + + *repos_p = repos; return SVN_NO_ERROR; }
diff --git a/subversion/tests/svn_test_fs.h b/subversion/tests/svn_test_fs.h index c50a482..11198c6 100644 --- a/subversion/tests/svn_test_fs.h +++ b/subversion/tests/svn_test_fs.h
@@ -41,23 +41,29 @@ svn_test__fs_new(svn_fs_t **fs_p, apr_pool_t *pool); -/* Create a filesystem of FS_TYPE in a subdir NAME and return a new FS - object which points to it. FS_TYPE should be either "bdb" or - "fsfs". Filesystem tests that are backend-specific should use - svn_test__create_fs instead of this. */ +/* Creates a filesystem which is always of type "bdb" in a subdir NAME + and return a new FS object which points to it. */ +svn_error_t * +svn_test__create_bdb_fs(svn_fs_t **fs_p, + const char *name, + apr_pool_t *pool); + + +/* Create a filesystem based on OPTS in a subdir NAME and return a new + FS object which points to it. */ svn_error_t * svn_test__create_fs(svn_fs_t **fs_p, const char *name, - const char *fs_type, + svn_test_opts_t *opts, apr_pool_t *pool); -/* Create a repository with a filesystem of FS_TYPE in a subdir NAME +/* Create a repository with a filesystem based on OPTS in a subdir NAME and return a new REPOS object which points to it. */ svn_error_t * svn_test__create_repos(svn_repos_t **repos_p, const char *name, - const char *fs_type, + svn_test_opts_t *opts, apr_pool_t *pool);
diff --git a/subversion/tests/svn_test_main.c b/subversion/tests/svn_test_main.c index 943f857..7b5020c 100644 --- a/subversion/tests/svn_test_main.c +++ b/subversion/tests/svn_test_main.c
@@ -57,13 +57,16 @@ fstype_opt, list_opt, verbose_opt, - quiet_opt + quiet_opt, + config_opt }; static const apr_getopt_option_t cl_options[] = { {"cleanup", cleanup_opt, 0, N_("remove test directories after success")}, + {"config-file", config_opt, 1, + N_("specify test config file ARG")}, {"fs-type", fstype_opt, 1, N_("specify a filesystem backend type ARG")}, {"list", list_opt, 0, @@ -185,6 +188,13 @@ /* Do test */ err = func(&msg, msg_only || skip, opts, pool); + if (err && err->apr_err == SVN_ERR_TEST_SKIPPED) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + skip = TRUE; + } + /* Failure means unexpected results -- FAIL or XPASS. */ test_failed = ((err != SVN_NO_ERROR) != (xfail != 0)); @@ -306,6 +316,9 @@ case cleanup_opt: cleanup_mode = 1; break; + case config_opt: + opts.config_file = apr_pstrdup(pool, opt_arg); + break; case fstype_opt: opts.fs_type = apr_pstrdup(pool, opt_arg); break;
diff --git a/subversion/tests/tests.conf b/subversion/tests/tests.conf new file mode 100644 index 0000000..1b3d643 --- /dev/null +++ b/subversion/tests/tests.conf
@@ -0,0 +1,19 @@ +### This config file configures some aspects of the Subversion test +### suite. Pass --config-file FILENAME to test programs if running +### them manually; "make check" passes this file in automatically. + +### Currently, it is used for two purposes: it is used to configure +### memcached for direct svn_cache/memcached tests in +### libsvn_subr/cache-test; and it is copied into new FSFS +### repositories as fsfs.conf (to configure their use of memcached as +### well). + +[memcached-servers] +### Run memcached servers and enter lines like the following (the key +### is ignored): +# key = 127.0.0.1:11211 + +[caches] +### In the test suite, we should make FSFS cache failures into actual +### test failures: +fail-stop = true