summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_subr/cache-memcache.c
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_subr/cache-memcache.c
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_subr/cache-memcache.c')
-rw-r--r--subversion/libsvn_subr/cache-memcache.c583
1 files changed, 583 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/cache-memcache.c b/subversion/libsvn_subr/cache-memcache.c
new file mode 100644
index 0000000..5332d04
--- /dev/null
+++ b/subversion/libsvn_subr/cache-memcache.c
@@ -0,0 +1,583 @@
+/*
+ * cache-memcache.c: memcached caching for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_md5.h>
+
+#include "svn_pools.h"
+#include "svn_base64.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+#include "private/svn_cache.h"
+#include "private/svn_dep_compat.h"
+
+#include "cache.h"
+
+#ifdef 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 memcache_t {
+ /* 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)
+
+
+/* Set *MC_KEY to a memcache key for the given key KEY for CACHE, allocated
+ in POOL. */
+static svn_error_t *
+build_key(const char **mc_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,
+ (char *)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)
+ {
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, long_key, long_key_len,
+ pool));
+
+ long_key = apr_pstrcat(pool,
+ apr_pstrmemdup(pool, long_key,
+ MEMCACHED_KEY_UNHASHED_LEN),
+ svn_checksum_to_cstring_display(checksum, pool),
+ (char *)NULL);
+ }
+
+ *mc_key = long_key;
+ return SVN_NO_ERROR;
+}
+
+/* Core functionality of our getter functions: fetch DATA from the memcached
+ * given by CACHE_VOID and identified by KEY. Indicate success in FOUND and
+ * use a tempoary sub-pool of POOL for allocations.
+ */
+static svn_error_t *
+memcache_internal_get(char **data,
+ apr_size_t *size,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *pool)
+{
+ memcache_t *cache = cache_void;
+ apr_status_t apr_err;
+ const char *mc_key;
+ apr_pool_t *subpool;
+
+ if (key == NULL)
+ {
+ *found = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ subpool = svn_pool_create(pool);
+ SVN_ERR(build_key(&mc_key, cache, key, subpool));
+
+ apr_err = apr_memcache_getp(cache->memcache,
+ pool,
+ mc_key,
+ data,
+ size,
+ 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"));
+
+ *found = TRUE;
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+memcache_get(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ memcache_t *cache = cache_void;
+ char *data;
+ apr_size_t data_len;
+ SVN_ERR(memcache_internal_get(&data,
+ &data_len,
+ found,
+ cache_void,
+ key,
+ result_pool));
+
+ /* If we found it, de-serialize it. */
+ if (*found)
+ {
+ if (cache->deserialize_func)
+ {
+ SVN_ERR((cache->deserialize_func)(value_p, data, data_len,
+ result_pool));
+ }
+ else
+ {
+ svn_string_t *value = apr_pcalloc(result_pool, sizeof(*value));
+ value->data = data;
+ value->len = data_len;
+ *value_p = value;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Core functionality of our setter functions: store LENGH bytes of DATA
+ * to be identified by KEY in the memcached given by CACHE_VOID. Use POOL
+ * for temporary allocations.
+ */
+static svn_error_t *
+memcache_internal_set(void *cache_void,
+ const void *key,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ memcache_t *cache = cache_void;
+ const char *mc_key;
+ apr_status_t apr_err;
+
+ SVN_ERR(build_key(&mc_key, cache, key, scratch_pool));
+ apr_err = apr_memcache_set(cache->memcache, mc_key, (char *)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"));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+memcache_set(void *cache_void,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ memcache_t *cache = cache_void;
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ void *data;
+ apr_size_t data_len;
+ svn_error_t *err;
+
+ if (key == NULL)
+ return SVN_NO_ERROR;
+
+ 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;
+ }
+
+ err = memcache_internal_set(cache_void, key, data, data_len, subpool);
+
+ svn_pool_destroy(subpool);
+ return err;
+}
+
+static svn_error_t *
+memcache_get_partial(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ char *data;
+ apr_size_t size;
+ SVN_ERR(memcache_internal_get(&data,
+ &size,
+ found,
+ cache_void,
+ key,
+ result_pool));
+
+ /* If we found it, de-serialize it. */
+ return *found
+ ? func(value_p, data, size, baton, result_pool)
+ : err;
+}
+
+
+static svn_error_t *
+memcache_set_partial(void *cache_void,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ void *data;
+ apr_size_t size;
+ svn_boolean_t found = FALSE;
+
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ SVN_ERR(memcache_internal_get((char **)&data,
+ &size,
+ &found,
+ cache_void,
+ key,
+ subpool));
+
+ /* If we found it, modify it and write it back to cache */
+ if (found)
+ {
+ SVN_ERR(func(&data, &size, baton, subpool));
+ err = memcache_internal_set(cache_void, key, data, size, subpool);
+ }
+
+ svn_pool_destroy(subpool);
+ return err;
+}
+
+
+static 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 *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can't iterate a memcached cache"));
+}
+
+static svn_boolean_t
+memcache_is_cachable(void *unused, apr_size_t size)
+{
+ (void)unused; /* silence gcc warning. */
+
+ /* The memcached cutoff seems to be a bit (header length?) under a megabyte.
+ * We round down a little to be safe.
+ */
+ return size < 1000000;
+}
+
+static svn_error_t *
+memcache_get_info(void *cache_void,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool)
+{
+ memcache_t *cache = cache_void;
+
+ info->id = apr_pstrdup(result_pool, cache->prefix);
+
+ /* we don't have any memory allocation info */
+
+ info->used_size = 0;
+ info->total_size = 0;
+ info->data_size = 0;
+ info->used_entries = 0;
+ info->total_entries = 0;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_cache__vtable_t memcache_vtable = {
+ memcache_get,
+ memcache_set,
+ memcache_iter,
+ memcache_is_cachable,
+ memcache_get_partial,
+ memcache_set_partial,
+ memcache_get_info
+};
+
+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 = 0;
+ wrapper->error_baton = 0;
+
+ *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 */
+ /* time to live (in microseconds) */
+ apr_time_from_sec(50),
+ &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)
+{
+ int 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 (server_count > APR_INT16_MAX)
+ return svn_error_create(SVN_ERR_TOO_MANY_MEMCACHED_SERVERS, NULL, NULL);
+
+#ifdef 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,
+ (apr_uint16_t)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 */
+}
OpenPOWER on IntegriCloud