cache-memcache.c revision 299742
1/* 2 * cache-memcache.c: memcached caching for Subversion 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include <apr_md5.h> 25 26#include "svn_pools.h" 27#include "svn_base64.h" 28#include "svn_path.h" 29 30#include "svn_private_config.h" 31#include "private/svn_cache.h" 32#include "private/svn_dep_compat.h" 33 34#include "cache.h" 35 36#ifdef SVN_HAVE_MEMCACHE 37 38#include <apr_memcache.h> 39 40/* A note on thread safety: 41 42 The apr_memcache_t object does its own mutex handling, and nothing 43 else in memcache_t is ever modified, so this implementation should 44 be fully thread-safe. 45*/ 46 47/* The (internal) cache object. */ 48typedef struct memcache_t { 49 /* The memcached server set we're using. */ 50 apr_memcache_t *memcache; 51 52 /* A prefix used to differentiate our data from any other data in 53 * the memcached (URI-encoded). */ 54 const char *prefix; 55 56 /* The size of the key: either a fixed number of bytes or 57 * APR_HASH_KEY_STRING. */ 58 apr_ssize_t klen; 59 60 61 /* Used to marshal values in and out of the cache. */ 62 svn_cache__serialize_func_t serialize_func; 63 svn_cache__deserialize_func_t deserialize_func; 64} memcache_t; 65 66/* The wrapper around apr_memcache_t. */ 67struct svn_memcache_t { 68 apr_memcache_t *c; 69}; 70 71 72/* The memcached protocol says the maximum key length is 250. Let's 73 just say 249, to be safe. */ 74#define MAX_MEMCACHED_KEY_LEN 249 75#define MEMCACHED_KEY_UNHASHED_LEN (MAX_MEMCACHED_KEY_LEN - \ 76 2 * APR_MD5_DIGESTSIZE) 77 78 79/* Set *MC_KEY to a memcache key for the given key KEY for CACHE, allocated 80 in POOL. */ 81static svn_error_t * 82build_key(const char **mc_key, 83 memcache_t *cache, 84 const void *raw_key, 85 apr_pool_t *pool) 86{ 87 const char *encoded_suffix; 88 const char *long_key; 89 apr_size_t long_key_len; 90 91 if (cache->klen == APR_HASH_KEY_STRING) 92 encoded_suffix = svn_path_uri_encode(raw_key, pool); 93 else 94 { 95 const svn_string_t *raw = svn_string_ncreate(raw_key, cache->klen, pool); 96 const svn_string_t *encoded = svn_base64_encode_string2(raw, FALSE, 97 pool); 98 encoded_suffix = encoded->data; 99 } 100 101 long_key = apr_pstrcat(pool, "SVN:", cache->prefix, ":", encoded_suffix, 102 SVN_VA_NULL); 103 long_key_len = strlen(long_key); 104 105 /* We don't want to have a key that's too big. If it was going to 106 be too big, we MD5 the entire string, then replace the last bit 107 with the checksum. Note that APR_MD5_DIGESTSIZE is for the pure 108 binary digest; we have to double that when we convert to hex. 109 110 Every key we use will either be at most 111 MEMCACHED_KEY_UNHASHED_LEN bytes long, or be exactly 112 MAX_MEMCACHED_KEY_LEN bytes long. */ 113 if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN) 114 { 115 svn_checksum_t *checksum; 116 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, long_key, long_key_len, 117 pool)); 118 119 long_key = apr_pstrcat(pool, 120 apr_pstrmemdup(pool, long_key, 121 MEMCACHED_KEY_UNHASHED_LEN), 122 svn_checksum_to_cstring_display(checksum, pool), 123 SVN_VA_NULL); 124 } 125 126 *mc_key = long_key; 127 return SVN_NO_ERROR; 128} 129 130/* Core functionality of our getter functions: fetch DATA from the memcached 131 * given by CACHE_VOID and identified by KEY. Indicate success in FOUND and 132 * use a tempoary sub-pool of POOL for allocations. 133 */ 134static svn_error_t * 135memcache_internal_get(char **data, 136 apr_size_t *size, 137 svn_boolean_t *found, 138 void *cache_void, 139 const void *key, 140 apr_pool_t *pool) 141{ 142 memcache_t *cache = cache_void; 143 apr_status_t apr_err; 144 const char *mc_key; 145 apr_pool_t *subpool; 146 147 if (key == NULL) 148 { 149 *found = FALSE; 150 return SVN_NO_ERROR; 151 } 152 153 subpool = svn_pool_create(pool); 154 SVN_ERR(build_key(&mc_key, cache, key, subpool)); 155 156 apr_err = apr_memcache_getp(cache->memcache, 157 pool, 158 mc_key, 159 data, 160 size, 161 NULL /* ignore flags */); 162 if (apr_err == APR_NOTFOUND) 163 { 164 *found = FALSE; 165 svn_pool_destroy(subpool); 166 return SVN_NO_ERROR; 167 } 168 else if (apr_err != APR_SUCCESS || !*data) 169 return svn_error_wrap_apr(apr_err, 170 _("Unknown memcached error while reading")); 171 172 *found = TRUE; 173 174 svn_pool_destroy(subpool); 175 return SVN_NO_ERROR; 176} 177 178 179static svn_error_t * 180memcache_get(void **value_p, 181 svn_boolean_t *found, 182 void *cache_void, 183 const void *key, 184 apr_pool_t *result_pool) 185{ 186 memcache_t *cache = cache_void; 187 char *data; 188 apr_size_t data_len; 189 SVN_ERR(memcache_internal_get(&data, 190 &data_len, 191 found, 192 cache_void, 193 key, 194 result_pool)); 195 196 /* If we found it, de-serialize it. */ 197 if (*found) 198 { 199 if (cache->deserialize_func) 200 { 201 SVN_ERR((cache->deserialize_func)(value_p, data, data_len, 202 result_pool)); 203 } 204 else 205 { 206 svn_stringbuf_t *value = svn_stringbuf_create_empty(result_pool); 207 value->data = data; 208 value->blocksize = data_len; 209 value->len = data_len - 1; /* account for trailing NUL */ 210 *value_p = value; 211 } 212 } 213 214 return SVN_NO_ERROR; 215} 216 217/* Implement vtable.has_key in terms of the getter. 218 */ 219static svn_error_t * 220memcache_has_key(svn_boolean_t *found, 221 void *cache_void, 222 const void *key, 223 apr_pool_t *scratch_pool) 224{ 225 char *data; 226 apr_size_t data_len; 227 SVN_ERR(memcache_internal_get(&data, 228 &data_len, 229 found, 230 cache_void, 231 key, 232 scratch_pool)); 233 234 return SVN_NO_ERROR; 235} 236 237/* Core functionality of our setter functions: store LENGH bytes of DATA 238 * to be identified by KEY in the memcached given by CACHE_VOID. Use POOL 239 * for temporary allocations. 240 */ 241static svn_error_t * 242memcache_internal_set(void *cache_void, 243 const void *key, 244 const char *data, 245 apr_size_t len, 246 apr_pool_t *scratch_pool) 247{ 248 memcache_t *cache = cache_void; 249 const char *mc_key; 250 apr_status_t apr_err; 251 252 SVN_ERR(build_key(&mc_key, cache, key, scratch_pool)); 253 apr_err = apr_memcache_set(cache->memcache, mc_key, (char *)data, len, 0, 0); 254 255 /* ### Maybe write failures should be ignored (but logged)? */ 256 if (apr_err != APR_SUCCESS) 257 return svn_error_wrap_apr(apr_err, 258 _("Unknown memcached error while writing")); 259 260 return SVN_NO_ERROR; 261} 262 263 264static svn_error_t * 265memcache_set(void *cache_void, 266 const void *key, 267 void *value, 268 apr_pool_t *scratch_pool) 269{ 270 memcache_t *cache = cache_void; 271 apr_pool_t *subpool = svn_pool_create(scratch_pool); 272 void *data; 273 apr_size_t data_len; 274 svn_error_t *err; 275 276 if (key == NULL) 277 return SVN_NO_ERROR; 278 279 if (cache->serialize_func) 280 { 281 SVN_ERR((cache->serialize_func)(&data, &data_len, value, subpool)); 282 } 283 else 284 { 285 svn_stringbuf_t *value_str = value; 286 data = value_str->data; 287 data_len = value_str->len + 1; /* copy trailing NUL */ 288 } 289 290 err = memcache_internal_set(cache_void, key, data, data_len, subpool); 291 292 svn_pool_destroy(subpool); 293 return err; 294} 295 296static svn_error_t * 297memcache_get_partial(void **value_p, 298 svn_boolean_t *found, 299 void *cache_void, 300 const void *key, 301 svn_cache__partial_getter_func_t func, 302 void *baton, 303 apr_pool_t *result_pool) 304{ 305 svn_error_t *err = SVN_NO_ERROR; 306 307 char *data; 308 apr_size_t size; 309 SVN_ERR(memcache_internal_get(&data, 310 &size, 311 found, 312 cache_void, 313 key, 314 result_pool)); 315 316 /* If we found it, de-serialize it. */ 317 return *found 318 ? func(value_p, data, size, baton, result_pool) 319 : err; 320} 321 322 323static svn_error_t * 324memcache_set_partial(void *cache_void, 325 const void *key, 326 svn_cache__partial_setter_func_t func, 327 void *baton, 328 apr_pool_t *scratch_pool) 329{ 330 svn_error_t *err = SVN_NO_ERROR; 331 332 void *data; 333 apr_size_t size; 334 svn_boolean_t found = FALSE; 335 336 apr_pool_t *subpool = svn_pool_create(scratch_pool); 337 SVN_ERR(memcache_internal_get((char **)&data, 338 &size, 339 &found, 340 cache_void, 341 key, 342 subpool)); 343 344 /* If we found it, modify it and write it back to cache */ 345 if (found) 346 { 347 SVN_ERR(func(&data, &size, baton, subpool)); 348 err = memcache_internal_set(cache_void, key, data, size, subpool); 349 } 350 351 svn_pool_destroy(subpool); 352 return err; 353} 354 355 356static svn_error_t * 357memcache_iter(svn_boolean_t *completed, 358 void *cache_void, 359 svn_iter_apr_hash_cb_t user_cb, 360 void *user_baton, 361 apr_pool_t *scratch_pool) 362{ 363 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 364 _("Can't iterate a memcached cache")); 365} 366 367static svn_boolean_t 368memcache_is_cachable(void *unused, apr_size_t size) 369{ 370 SVN_UNUSED(unused); 371 372 /* The memcached cutoff seems to be a bit (header length?) under a megabyte. 373 * We round down a little to be safe. 374 */ 375 return size < 1000000; 376} 377 378static svn_error_t * 379memcache_get_info(void *cache_void, 380 svn_cache__info_t *info, 381 svn_boolean_t reset, 382 apr_pool_t *result_pool) 383{ 384 memcache_t *cache = cache_void; 385 386 info->id = apr_pstrdup(result_pool, cache->prefix); 387 388 /* we don't have any memory allocation info */ 389 390 return SVN_NO_ERROR; 391} 392 393static svn_cache__vtable_t memcache_vtable = { 394 memcache_get, 395 memcache_has_key, 396 memcache_set, 397 memcache_iter, 398 memcache_is_cachable, 399 memcache_get_partial, 400 memcache_set_partial, 401 memcache_get_info 402}; 403 404svn_error_t * 405svn_cache__create_memcache(svn_cache__t **cache_p, 406 svn_memcache_t *memcache, 407 svn_cache__serialize_func_t serialize_func, 408 svn_cache__deserialize_func_t deserialize_func, 409 apr_ssize_t klen, 410 const char *prefix, 411 apr_pool_t *pool) 412{ 413 svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper)); 414 memcache_t *cache = apr_pcalloc(pool, sizeof(*cache)); 415 416 cache->serialize_func = serialize_func; 417 cache->deserialize_func = deserialize_func; 418 cache->klen = klen; 419 cache->prefix = svn_path_uri_encode(prefix, pool); 420 cache->memcache = memcache->c; 421 422 wrapper->vtable = &memcache_vtable; 423 wrapper->cache_internal = cache; 424 wrapper->error_handler = 0; 425 wrapper->error_baton = 0; 426 wrapper->pretend_empty = !!getenv("SVN_X_DOES_NOT_MARK_THE_SPOT"); 427 428 *cache_p = wrapper; 429 return SVN_NO_ERROR; 430} 431 432 433/*** Creating apr_memcache_t from svn_config_t. ***/ 434 435/* Baton for add_memcache_server. */ 436struct ams_baton { 437 apr_memcache_t *memcache; 438 apr_pool_t *memcache_pool; 439 svn_error_t *err; 440}; 441 442/* Implements svn_config_enumerator2_t. */ 443static svn_boolean_t 444add_memcache_server(const char *name, 445 const char *value, 446 void *baton, 447 apr_pool_t *pool) 448{ 449 struct ams_baton *b = baton; 450 char *host, *scope; 451 apr_port_t port; 452 apr_status_t apr_err; 453 apr_memcache_server_t *server; 454 455 apr_err = apr_parse_addr_port(&host, &scope, &port, 456 value, pool); 457 if (apr_err != APR_SUCCESS) 458 { 459 b->err = svn_error_wrap_apr(apr_err, 460 _("Error parsing memcache server '%s'"), 461 name); 462 return FALSE; 463 } 464 465 if (scope) 466 { 467 b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL, 468 _("Scope not allowed in memcache server " 469 "'%s'"), 470 name); 471 return FALSE; 472 } 473 if (!host || !port) 474 { 475 b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL, 476 _("Must specify host and port for memcache " 477 "server '%s'"), 478 name); 479 return FALSE; 480 } 481 482 /* Note: the four numbers here are only relevant when an 483 apr_memcache_t is being shared by multiple threads. */ 484 apr_err = apr_memcache_server_create(b->memcache_pool, 485 host, 486 port, 487 0, /* min connections */ 488 5, /* soft max connections */ 489 10, /* hard max connections */ 490 /* time to live (in microseconds) */ 491 apr_time_from_sec(50), 492 &server); 493 if (apr_err != APR_SUCCESS) 494 { 495 b->err = svn_error_wrap_apr(apr_err, 496 _("Unknown error creating memcache server")); 497 return FALSE; 498 } 499 500 apr_err = apr_memcache_add_server(b->memcache, server); 501 if (apr_err != APR_SUCCESS) 502 { 503 b->err = svn_error_wrap_apr(apr_err, 504 _("Unknown error adding server to memcache")); 505 return FALSE; 506 } 507 508 return TRUE; 509} 510 511#else /* ! SVN_HAVE_MEMCACHE */ 512 513/* Stubs for no apr memcache library. */ 514 515struct svn_memcache_t { 516 void *unused; /* Let's not have a size-zero struct. */ 517}; 518 519svn_error_t * 520svn_cache__create_memcache(svn_cache__t **cache_p, 521 svn_memcache_t *memcache, 522 svn_cache__serialize_func_t serialize_func, 523 svn_cache__deserialize_func_t deserialize_func, 524 apr_ssize_t klen, 525 const char *prefix, 526 apr_pool_t *pool) 527{ 528 return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL); 529} 530 531#endif /* SVN_HAVE_MEMCACHE */ 532 533/* Implements svn_config_enumerator2_t. Just used for the 534 entry-counting return value of svn_config_enumerate2. */ 535static svn_boolean_t 536nop_enumerator(const char *name, 537 const char *value, 538 void *baton, 539 apr_pool_t *pool) 540{ 541 return TRUE; 542} 543 544svn_error_t * 545svn_cache__make_memcache_from_config(svn_memcache_t **memcache_p, 546 svn_config_t *config, 547 apr_pool_t *result_pool, 548 apr_pool_t *scratch_pool) 549{ 550 int server_count = 551 svn_config_enumerate2(config, 552 SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS, 553 nop_enumerator, NULL, scratch_pool); 554 555 if (server_count == 0) 556 { 557 *memcache_p = NULL; 558 return SVN_NO_ERROR; 559 } 560 561 if (server_count > APR_INT16_MAX) 562 return svn_error_create(SVN_ERR_TOO_MANY_MEMCACHED_SERVERS, NULL, NULL); 563 564#ifdef SVN_HAVE_MEMCACHE 565 { 566 struct ams_baton b; 567 svn_memcache_t *memcache = apr_pcalloc(result_pool, sizeof(*memcache)); 568 apr_status_t apr_err = apr_memcache_create(result_pool, 569 (apr_uint16_t)server_count, 570 0, /* flags */ 571 &(memcache->c)); 572 if (apr_err != APR_SUCCESS) 573 return svn_error_wrap_apr(apr_err, 574 _("Unknown error creating apr_memcache_t")); 575 576 b.memcache = memcache->c; 577 b.memcache_pool = result_pool; 578 b.err = SVN_NO_ERROR; 579 svn_config_enumerate2(config, 580 SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS, 581 add_memcache_server, &b, 582 scratch_pool); 583 584 if (b.err) 585 return b.err; 586 587 *memcache_p = memcache; 588 589 return SVN_NO_ERROR; 590 } 591#else /* ! SVN_HAVE_MEMCACHE */ 592 { 593 return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL); 594 } 595#endif /* SVN_HAVE_MEMCACHE */ 596} 597