caching.c revision 362181
1/* caching.c : in-memory caching 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23#include "fs.h" 24#include "fs_fs.h" 25#include "id.h" 26#include "dag.h" 27#include "tree.h" 28#include "index.h" 29#include "temp_serializer.h" 30#include "../libsvn_fs/fs-loader.h" 31 32#include "svn_config.h" 33#include "svn_cache_config.h" 34 35#include "svn_private_config.h" 36#include "svn_hash.h" 37#include "svn_pools.h" 38 39#include "private/svn_debug.h" 40#include "private/svn_subr_private.h" 41 42/* Take the ORIGINAL string and replace all occurrences of ":" without 43 * limiting the key space. Allocate the result in POOL. 44 */ 45static const char * 46normalize_key_part(const char *original, 47 apr_pool_t *pool) 48{ 49 apr_size_t i; 50 apr_size_t len = strlen(original); 51 svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool); 52 53 for (i = 0; i < len; ++i) 54 { 55 char c = original[i]; 56 switch (c) 57 { 58 case ':': svn_stringbuf_appendbytes(normalized, "%_", 2); 59 break; 60 case '%': svn_stringbuf_appendbytes(normalized, "%%", 2); 61 break; 62 default : svn_stringbuf_appendbyte(normalized, c); 63 } 64 } 65 66 return normalized->data; 67} 68 69/* *CACHE_TXDELTAS, *CACHE_FULLTEXTS, *CACHE_NODEPROPS flags will be set 70 according to FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to 71 use. 72 73 Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL 74 for temporary allocations. */ 75static svn_error_t * 76read_config(const char **cache_namespace, 77 svn_boolean_t *cache_txdeltas, 78 svn_boolean_t *cache_fulltexts, 79 svn_boolean_t *cache_nodeprops, 80 svn_fs_t *fs, 81 apr_pool_t *pool) 82{ 83 /* No cache namespace by default. I.e. all FS instances share the 84 * cached data. If you specify different namespaces, the data will 85 * share / compete for the same cache memory but keys will not match 86 * across namespaces and, thus, cached data will not be shared between 87 * namespaces. 88 * 89 * Since the namespace will be concatenated with other elements to form 90 * the complete key prefix, we must make sure that the resulting string 91 * is unique and cannot be created by any other combination of elements. 92 */ 93 *cache_namespace 94 = normalize_key_part(svn_hash__get_cstring(fs->config, 95 SVN_FS_CONFIG_FSFS_CACHE_NS, 96 ""), 97 pool); 98 99 /* Cache text deltas by default. 100 * They tend to be smaller and have finer granularity than fulltexts. 101 */ 102 *cache_txdeltas 103 = svn_hash__get_bool(fs->config, 104 SVN_FS_CONFIG_FSFS_CACHE_DELTAS, 105 TRUE); 106 107 /* by default, cache fulltexts. 108 * Most SVN tools care about reconstructed file content. 109 * Thus, this is a reasonable default. 110 * SVN admin tools may set that to FALSE because fulltexts 111 * won't be re-used rendering the cache less effective 112 * by squeezing wanted data out. 113 */ 114 *cache_fulltexts 115 = svn_hash__get_bool(fs->config, 116 SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, 117 TRUE); 118 119 /* by default, cache nodeprops. 120 * Pre-1.10, this was controlled by the SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS 121 * configuration option which defaulted to TRUE. 122 */ 123 *cache_nodeprops 124 = svn_hash__get_bool(fs->config, 125 SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS, 126 TRUE); 127 return SVN_NO_ERROR; 128} 129 130 131/* Implements svn_cache__error_handler_t 132 * This variant clears the error after logging it. 133 */ 134static svn_error_t * 135warn_and_continue_on_cache_errors(svn_error_t *err, 136 void *baton, 137 apr_pool_t *pool) 138{ 139 svn_fs_t *fs = baton; 140 (fs->warning)(fs->warning_baton, err); 141 svn_error_clear(err); 142 143 return SVN_NO_ERROR; 144} 145 146/* Implements svn_cache__error_handler_t 147 * This variant logs the error and passes it on to the callers. 148 */ 149static svn_error_t * 150warn_and_fail_on_cache_errors(svn_error_t *err, 151 void *baton, 152 apr_pool_t *pool) 153{ 154 svn_fs_t *fs = baton; 155 (fs->warning)(fs->warning_baton, err); 156 return err; 157} 158 159#ifdef SVN_DEBUG_CACHE_DUMP_STATS 160/* Baton to be used for the dump_cache_statistics() pool cleanup function, */ 161struct dump_cache_baton_t 162{ 163 /* the pool about to be cleaned up. Will be used for temp. allocations. */ 164 apr_pool_t *pool; 165 166 /* the cache to dump the statistics for */ 167 svn_cache__t *cache; 168}; 169 170/* APR pool cleanup handler that will printf the statistics of the 171 cache referenced by the baton in BATON_VOID. */ 172static apr_status_t 173dump_cache_statistics(void *baton_void) 174{ 175 struct dump_cache_baton_t *baton = baton_void; 176 177 apr_status_t result = APR_SUCCESS; 178 svn_cache__info_t info; 179 svn_string_t *text_stats; 180 apr_array_header_t *lines; 181 int i; 182 183 svn_error_t *err = svn_cache__get_info(baton->cache, 184 &info, 185 TRUE, 186 baton->pool); 187 188 /* skip unused caches */ 189 if (! err && (info.gets > 0 || info.sets > 0)) 190 { 191 text_stats = svn_cache__format_info(&info, TRUE, baton->pool); 192 lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool); 193 194 for (i = 0; i < lines->nelts; ++i) 195 { 196 const char *line = APR_ARRAY_IDX(lines, i, const char *); 197#ifdef SVN_DEBUG 198 SVN_DBG(("%s\n", line)); 199#endif 200 } 201 } 202 203 /* process error returns */ 204 if (err) 205 { 206 result = err->apr_err; 207 svn_error_clear(err); 208 } 209 210 return result; 211} 212 213static apr_status_t 214dump_global_cache_statistics(void *baton_void) 215{ 216 apr_pool_t *pool = baton_void; 217 218 svn_cache__info_t *info = svn_cache__membuffer_get_global_info(pool); 219 svn_string_t *text_stats = svn_cache__format_info(info, FALSE, pool); 220 apr_array_header_t *lines = svn_cstring_split(text_stats->data, "\n", 221 FALSE, pool); 222 223 int i; 224 for (i = 0; i < lines->nelts; ++i) 225 { 226 const char *line = APR_ARRAY_IDX(lines, i, const char *); 227#ifdef SVN_DEBUG 228 SVN_DBG(("%s\n", line)); 229#endif 230 } 231 232 return APR_SUCCESS; 233} 234 235#endif /* SVN_DEBUG_CACHE_DUMP_STATS */ 236 237/* This function sets / registers the required callbacks for a given 238 * not transaction-specific CACHE object in FS, if CACHE is not NULL. 239 * 240 * All these svn_cache__t instances shall be handled uniformly. Unless 241 * ERROR_HANDLER is NULL, register it for the given CACHE in FS. 242 */ 243static svn_error_t * 244init_callbacks(svn_cache__t *cache, 245 svn_fs_t *fs, 246 svn_cache__error_handler_t error_handler, 247 apr_pool_t *pool) 248{ 249 if (cache != NULL) 250 { 251#ifdef SVN_DEBUG_CACHE_DUMP_STATS 252 253 /* schedule printing the access statistics upon pool cleanup, 254 * i.e. end of FSFS session. 255 */ 256 struct dump_cache_baton_t *baton; 257 258 baton = apr_palloc(pool, sizeof(*baton)); 259 baton->pool = pool; 260 baton->cache = cache; 261 262 apr_pool_cleanup_register(pool, 263 baton, 264 dump_cache_statistics, 265 apr_pool_cleanup_null); 266#endif 267 268 if (error_handler) 269 SVN_ERR(svn_cache__set_error_handler(cache, 270 error_handler, 271 fs, 272 pool)); 273 274 } 275 276 return SVN_NO_ERROR; 277} 278 279/* Sets *CACHE_P to cache instance based on provided options. 280 * Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if 281 * MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and 282 * MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL 283 * otherwise. Use the given PRIORITY class for the new cache. If it 284 * is 0, then use the default priority class. HAS_NAMESPACE indicates 285 * whether we prefixed this cache instance with a namespace. 286 * 287 * Unless NO_HANDLER is true, register an error handler that reports errors 288 * as warnings to the FS warning callback. 289 * 290 * Cache is allocated in RESULT_POOL, temporaries in SCRATCH_POOL. 291 * */ 292static svn_error_t * 293create_cache(svn_cache__t **cache_p, 294 svn_memcache_t *memcache, 295 svn_membuffer_t *membuffer, 296 apr_int64_t pages, 297 apr_int64_t items_per_page, 298 svn_cache__serialize_func_t serializer, 299 svn_cache__deserialize_func_t deserializer, 300 apr_ssize_t klen, 301 const char *prefix, 302 apr_uint32_t priority, 303 svn_boolean_t has_namespace, 304 svn_fs_t *fs, 305 svn_boolean_t no_handler, 306 apr_pool_t *result_pool, 307 apr_pool_t *scratch_pool) 308{ 309 svn_cache__error_handler_t error_handler = no_handler 310 ? NULL 311 : warn_and_fail_on_cache_errors; 312 if (priority == 0) 313 priority = SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY; 314 315 if (memcache) 316 { 317 SVN_ERR(svn_cache__create_memcache(cache_p, memcache, 318 serializer, deserializer, klen, 319 prefix, result_pool)); 320 error_handler = no_handler 321 ? NULL 322 : warn_and_continue_on_cache_errors; 323 } 324 else if (membuffer) 325 { 326 /* We assume caches with namespaces to be relatively short-lived, 327 * i.e. their data will not be needed after a while. */ 328 SVN_ERR(svn_cache__create_membuffer_cache( 329 cache_p, membuffer, serializer, deserializer, 330 klen, prefix, priority, FALSE, has_namespace, 331 result_pool, scratch_pool)); 332 } 333 else if (pages) 334 { 335 SVN_ERR(svn_cache__create_inprocess( 336 cache_p, serializer, deserializer, klen, pages, 337 items_per_page, FALSE, prefix, result_pool)); 338 } 339 else 340 { 341 *cache_p = NULL; 342 } 343 344 SVN_ERR(init_callbacks(*cache_p, fs, error_handler, result_pool)); 345 346 return SVN_NO_ERROR; 347} 348 349svn_error_t * 350svn_fs_fs__initialize_caches(svn_fs_t *fs, 351 apr_pool_t *pool) 352{ 353 fs_fs_data_t *ffd = fs->fsap_data; 354 const char *prefix = apr_pstrcat(pool, 355 "fsfs:", fs->uuid, 356 "/", normalize_key_part(fs->path, pool), 357 ":", 358 SVN_VA_NULL); 359 svn_membuffer_t *membuffer; 360 svn_boolean_t no_handler = ffd->fail_stop; 361 svn_boolean_t cache_txdeltas; 362 svn_boolean_t cache_fulltexts; 363 svn_boolean_t cache_nodeprops; 364 const char *cache_namespace; 365 svn_boolean_t has_namespace; 366 367 /* Evaluating the cache configuration. */ 368 SVN_ERR(read_config(&cache_namespace, 369 &cache_txdeltas, 370 &cache_fulltexts, 371 &cache_nodeprops, 372 fs, 373 pool)); 374 375 prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, SVN_VA_NULL); 376 has_namespace = strlen(cache_namespace) > 0; 377 378 membuffer = svn_cache__get_global_membuffer_cache(); 379 380 /* General rules for assigning cache priorities: 381 * 382 * - Data that can be reconstructed from other elements has low prio 383 * (e.g. fulltexts etc.) 384 * - Index data required to find any of the other data has high prio 385 * (e.g. noderevs, L2P and P2L index pages) 386 * - everything else should use default prio 387 */ 388 389#ifdef SVN_DEBUG_CACHE_DUMP_STATS 390 391 /* schedule printing the global access statistics upon pool cleanup, 392 * i.e. when the repo instance gets closed / cleaned up. 393 */ 394 if (membuffer) 395 apr_pool_cleanup_register(fs->pool, 396 fs->pool, 397 dump_global_cache_statistics, 398 apr_pool_cleanup_null); 399#endif 400 401 /* Make the cache for revision roots. For the vast majority of 402 * commands, this is only going to contain a few entries (svnadmin 403 * dump/verify is an exception here), so to reduce overhead let's 404 * try to keep it to just one page. I estimate each entry has about 405 * 130 bytes of overhead (svn_revnum_t key, ID struct, and the cache_entry); 406 * the default pool size is 8192, so about a fifty should fit comfortably. 407 */ 408 SVN_ERR(create_cache(&(ffd->rev_root_id_cache), 409 NULL, 410 membuffer, 411 1, 50, 412 svn_fs_fs__serialize_id, 413 svn_fs_fs__deserialize_id, 414 sizeof(svn_revnum_t), 415 apr_pstrcat(pool, prefix, "RRI", SVN_VA_NULL), 416 0, 417 has_namespace, 418 fs, 419 no_handler, 420 fs->pool, pool)); 421 422 /* Rough estimate: revision DAG nodes have size around 1kBytes, so 423 * let's put 8 on a page. */ 424 SVN_ERR(create_cache(&(ffd->rev_node_cache), 425 NULL, 426 membuffer, 427 1, 8, 428 svn_fs_fs__dag_serialize, 429 svn_fs_fs__dag_deserialize, 430 APR_HASH_KEY_STRING, 431 apr_pstrcat(pool, prefix, "DAG", SVN_VA_NULL), 432 SVN_CACHE__MEMBUFFER_LOW_PRIORITY, 433 has_namespace, 434 fs, 435 no_handler, 436 fs->pool, pool)); 437 438 /* 1st level DAG node cache */ 439 ffd->dag_node_cache = svn_fs_fs__create_dag_cache(fs->pool); 440 441 /* Very rough estimate: 1K per directory. */ 442 SVN_ERR(create_cache(&(ffd->dir_cache), 443 NULL, 444 membuffer, 445 1, 8, 446 svn_fs_fs__serialize_dir_entries, 447 svn_fs_fs__deserialize_dir_entries, 448 sizeof(pair_cache_key_t), 449 apr_pstrcat(pool, prefix, "DIR", SVN_VA_NULL), 450 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 451 has_namespace, 452 fs, 453 no_handler, 454 fs->pool, pool)); 455 456 /* 8 kBytes per entry (1000 revs / shared, one file offset per rev). 457 Covering about 8 pack files gives us an "o.k." hit rate. */ 458 SVN_ERR(create_cache(&(ffd->packed_offset_cache), 459 NULL, 460 membuffer, 461 8, 1, 462 svn_fs_fs__serialize_manifest, 463 svn_fs_fs__deserialize_manifest, 464 sizeof(svn_revnum_t), 465 apr_pstrcat(pool, prefix, "PACK-MANIFEST", 466 SVN_VA_NULL), 467 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 468 has_namespace, 469 fs, 470 no_handler, 471 fs->pool, pool)); 472 473 /* initialize node revision cache, if caching has been enabled */ 474 SVN_ERR(create_cache(&(ffd->node_revision_cache), 475 NULL, 476 membuffer, 477 2, 16, /* ~500 byte / entry; 32 entries total */ 478 svn_fs_fs__serialize_node_revision, 479 svn_fs_fs__deserialize_node_revision, 480 sizeof(pair_cache_key_t), 481 apr_pstrcat(pool, prefix, "NODEREVS", SVN_VA_NULL), 482 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 483 has_namespace, 484 fs, 485 no_handler, 486 fs->pool, pool)); 487 488 /* initialize representation header cache, if caching has been enabled */ 489 SVN_ERR(create_cache(&(ffd->rep_header_cache), 490 NULL, 491 membuffer, 492 1, 200, /* ~40 bytes / entry; 200 entries total */ 493 svn_fs_fs__serialize_rep_header, 494 svn_fs_fs__deserialize_rep_header, 495 sizeof(pair_cache_key_t), 496 apr_pstrcat(pool, prefix, "REPHEADER", SVN_VA_NULL), 497 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY, 498 has_namespace, 499 fs, 500 no_handler, 501 fs->pool, pool)); 502 503 /* initialize node change list cache, if caching has been enabled */ 504 SVN_ERR(create_cache(&(ffd->changes_cache), 505 NULL, 506 membuffer, 507 1, 8, /* 1k / entry; 8 entries total, rarely used */ 508 svn_fs_fs__serialize_changes, 509 svn_fs_fs__deserialize_changes, 510 sizeof(pair_cache_key_t), 511 apr_pstrcat(pool, prefix, "CHANGES", SVN_VA_NULL), 512 0, 513 has_namespace, 514 fs, 515 no_handler, 516 fs->pool, pool)); 517 518 /* if enabled, cache revprops */ 519 SVN_ERR(create_cache(&(ffd->revprop_cache), 520 NULL, 521 membuffer, 522 8, 20, /* ~400 bytes / entry, capa for ~2 packs */ 523 svn_fs_fs__serialize_revprops, 524 svn_fs_fs__deserialize_revprops, 525 sizeof(pair_cache_key_t), 526 apr_pstrcat(pool, prefix, "REVPROP", SVN_VA_NULL), 527 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY, 528 TRUE, /* contents is short-lived */ 529 fs, 530 no_handler, 531 fs->pool, pool)); 532 533 /* if enabled, cache fulltext and other derived information */ 534 if (cache_fulltexts) 535 { 536 SVN_ERR(create_cache(&(ffd->fulltext_cache), 537 ffd->memcache, 538 membuffer, 539 0, 0, /* Do not use the inprocess cache */ 540 /* Values are svn_stringbuf_t */ 541 NULL, NULL, 542 sizeof(pair_cache_key_t), 543 apr_pstrcat(pool, prefix, "TEXT", SVN_VA_NULL), 544 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY, 545 has_namespace, 546 fs, 547 no_handler, 548 fs->pool, pool)); 549 550 SVN_ERR(create_cache(&(ffd->mergeinfo_cache), 551 NULL, 552 membuffer, 553 0, 0, /* Do not use the inprocess cache */ 554 svn_fs_fs__serialize_mergeinfo, 555 svn_fs_fs__deserialize_mergeinfo, 556 APR_HASH_KEY_STRING, 557 apr_pstrcat(pool, prefix, "MERGEINFO", 558 SVN_VA_NULL), 559 0, 560 has_namespace, 561 fs, 562 no_handler, 563 fs->pool, pool)); 564 565 SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache), 566 NULL, 567 membuffer, 568 0, 0, /* Do not use the inprocess cache */ 569 /* Values are svn_stringbuf_t */ 570 NULL, NULL, 571 APR_HASH_KEY_STRING, 572 apr_pstrcat(pool, prefix, "HAS_MERGEINFO", 573 SVN_VA_NULL), 574 0, 575 has_namespace, 576 fs, 577 no_handler, 578 fs->pool, pool)); 579 } 580 else 581 { 582 ffd->fulltext_cache = NULL; 583 ffd->mergeinfo_cache = NULL; 584 ffd->mergeinfo_existence_cache = NULL; 585 } 586 587 /* if enabled, cache node properties */ 588 if (cache_nodeprops) 589 { 590 SVN_ERR(create_cache(&(ffd->properties_cache), 591 NULL, 592 membuffer, 593 0, 0, /* Do not use the inprocess cache */ 594 svn_fs_fs__serialize_properties, 595 svn_fs_fs__deserialize_properties, 596 sizeof(pair_cache_key_t), 597 apr_pstrcat(pool, prefix, "PROP", 598 SVN_VA_NULL), 599 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY, 600 has_namespace, 601 fs, 602 no_handler, 603 fs->pool, pool)); 604 } 605 else 606 { 607 ffd->properties_cache = NULL; 608 } 609 610 /* if enabled, cache text deltas and their combinations */ 611 if (cache_txdeltas) 612 { 613 SVN_ERR(create_cache(&(ffd->raw_window_cache), 614 NULL, 615 membuffer, 616 0, 0, /* Do not use the inprocess cache */ 617 svn_fs_fs__serialize_raw_window, 618 svn_fs_fs__deserialize_raw_window, 619 sizeof(window_cache_key_t), 620 apr_pstrcat(pool, prefix, "RAW_WINDOW", 621 SVN_VA_NULL), 622 SVN_CACHE__MEMBUFFER_LOW_PRIORITY, 623 has_namespace, 624 fs, 625 no_handler, 626 fs->pool, pool)); 627 628 SVN_ERR(create_cache(&(ffd->txdelta_window_cache), 629 NULL, 630 membuffer, 631 0, 0, /* Do not use the inprocess cache */ 632 svn_fs_fs__serialize_txdelta_window, 633 svn_fs_fs__deserialize_txdelta_window, 634 sizeof(window_cache_key_t), 635 apr_pstrcat(pool, prefix, "TXDELTA_WINDOW", 636 SVN_VA_NULL), 637 SVN_CACHE__MEMBUFFER_LOW_PRIORITY, 638 has_namespace, 639 fs, 640 no_handler, 641 fs->pool, pool)); 642 643 SVN_ERR(create_cache(&(ffd->combined_window_cache), 644 NULL, 645 membuffer, 646 0, 0, /* Do not use the inprocess cache */ 647 /* Values are svn_stringbuf_t */ 648 NULL, NULL, 649 sizeof(window_cache_key_t), 650 apr_pstrcat(pool, prefix, "COMBINED_WINDOW", 651 SVN_VA_NULL), 652 SVN_CACHE__MEMBUFFER_LOW_PRIORITY, 653 has_namespace, 654 fs, 655 no_handler, 656 fs->pool, pool)); 657 } 658 else 659 { 660 ffd->txdelta_window_cache = NULL; 661 ffd->combined_window_cache = NULL; 662 } 663 664 SVN_ERR(create_cache(&(ffd->l2p_header_cache), 665 NULL, 666 membuffer, 667 8, 16, /* entry size varies but we must cover a 668 reasonable number of rev / pack files 669 to allow for delta chains to be walked 670 efficiently etc. */ 671 svn_fs_fs__serialize_l2p_header, 672 svn_fs_fs__deserialize_l2p_header, 673 sizeof(pair_cache_key_t), 674 apr_pstrcat(pool, prefix, "L2P_HEADER", 675 (char *)NULL), 676 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 677 has_namespace, 678 fs, 679 no_handler, 680 fs->pool, pool)); 681 SVN_ERR(create_cache(&(ffd->l2p_page_cache), 682 NULL, 683 membuffer, 684 8, 16, /* entry size varies but we must cover a 685 reasonable number of rev / pack files 686 to allow for delta chains to be walked 687 efficiently etc. */ 688 svn_fs_fs__serialize_l2p_page, 689 svn_fs_fs__deserialize_l2p_page, 690 sizeof(svn_fs_fs__page_cache_key_t), 691 apr_pstrcat(pool, prefix, "L2P_PAGE", 692 (char *)NULL), 693 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 694 has_namespace, 695 fs, 696 no_handler, 697 fs->pool, pool)); 698 SVN_ERR(create_cache(&(ffd->p2l_header_cache), 699 NULL, 700 membuffer, 701 4, 1, /* Large entries. Rarely used. */ 702 svn_fs_fs__serialize_p2l_header, 703 svn_fs_fs__deserialize_p2l_header, 704 sizeof(pair_cache_key_t), 705 apr_pstrcat(pool, prefix, "P2L_HEADER", 706 (char *)NULL), 707 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 708 has_namespace, 709 fs, 710 no_handler, 711 fs->pool, pool)); 712 SVN_ERR(create_cache(&(ffd->p2l_page_cache), 713 NULL, 714 membuffer, 715 4, 1, /* Variably sized entries. Rarely used. */ 716 svn_fs_fs__serialize_p2l_page, 717 svn_fs_fs__deserialize_p2l_page, 718 sizeof(svn_fs_fs__page_cache_key_t), 719 apr_pstrcat(pool, prefix, "P2L_PAGE", 720 (char *)NULL), 721 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 722 has_namespace, 723 fs, 724 no_handler, 725 fs->pool, pool)); 726 727 return SVN_NO_ERROR; 728} 729 730/* Baton to be used for the remove_txn_cache() pool cleanup function, */ 731struct txn_cleanup_baton_t 732{ 733 /* the cache to reset */ 734 svn_cache__t *txn_cache; 735 736 /* the position where to reset it */ 737 svn_cache__t **to_reset; 738 739 /* pool that TXN_CACHE was allocated in */ 740 apr_pool_t *txn_pool; 741 742 /* pool that the FS containing the TO_RESET pointer was allocator */ 743 apr_pool_t *fs_pool; 744}; 745 746/* Forward declaration. */ 747static apr_status_t 748remove_txn_cache_fs(void *baton_void); 749 750/* APR pool cleanup handler that will reset the cache pointer given in 751 BATON_VOID when the TXN_POOL gets cleaned up. */ 752static apr_status_t 753remove_txn_cache_txn(void *baton_void) 754{ 755 struct txn_cleanup_baton_t *baton = baton_void; 756 757 /* be careful not to hurt performance by resetting newer txn's caches. */ 758 if (*baton->to_reset == baton->txn_cache) 759 { 760 /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */ 761 *baton->to_reset = NULL; 762 } 763 764 /* It's cleaned up now. Prevent double cleanup. */ 765 apr_pool_cleanup_kill(baton->fs_pool, 766 baton, 767 remove_txn_cache_fs); 768 769 return APR_SUCCESS; 770} 771 772/* APR pool cleanup handler that will reset the cache pointer given in 773 BATON_VOID when the FS_POOL gets cleaned up. */ 774static apr_status_t 775remove_txn_cache_fs(void *baton_void) 776{ 777 struct txn_cleanup_baton_t *baton = baton_void; 778 779 /* be careful not to hurt performance by resetting newer txn's caches. */ 780 if (*baton->to_reset == baton->txn_cache) 781 { 782 /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */ 783 *baton->to_reset = NULL; 784 } 785 786 /* It's cleaned up now. Prevent double cleanup. */ 787 apr_pool_cleanup_kill(baton->txn_pool, 788 baton, 789 remove_txn_cache_txn); 790 791 return APR_SUCCESS; 792} 793 794/* This function sets / registers the required callbacks for a given 795 * transaction-specific *CACHE object in FS, if CACHE is not NULL and 796 * a no-op otherwise. In particular, it will ensure that *CACHE gets 797 * reset to NULL upon POOL or FS->POOL destruction latest. 798 */ 799static void 800init_txn_callbacks(svn_fs_t *fs, 801 svn_cache__t **cache, 802 apr_pool_t *pool) 803{ 804 if (*cache != NULL) 805 { 806 struct txn_cleanup_baton_t *baton; 807 808 baton = apr_palloc(pool, sizeof(*baton)); 809 baton->txn_cache = *cache; 810 baton->to_reset = cache; 811 baton->txn_pool = pool; 812 baton->fs_pool = fs->pool; 813 814 /* If any of these pools gets cleaned, we must reset the cache. 815 * We don't know which one will get cleaned up first, so register 816 * cleanup actions for both and during the cleanup action, unregister 817 * the respective other action. */ 818 apr_pool_cleanup_register(pool, 819 baton, 820 remove_txn_cache_txn, 821 apr_pool_cleanup_null); 822 apr_pool_cleanup_register(fs->pool, 823 baton, 824 remove_txn_cache_fs, 825 apr_pool_cleanup_null); 826 } 827} 828 829svn_error_t * 830svn_fs_fs__initialize_txn_caches(svn_fs_t *fs, 831 const char *txn_id, 832 apr_pool_t *pool) 833{ 834 fs_fs_data_t *ffd = fs->fsap_data; 835 const char *prefix; 836 837 /* We don't support caching for concurrent transactions in the SAME 838 * FSFS session. Maybe, you forgot to clean POOL. */ 839 if (ffd->txn_dir_cache != NULL || ffd->concurrent_transactions) 840 { 841 ffd->txn_dir_cache = NULL; 842 ffd->concurrent_transactions = TRUE; 843 844 return SVN_NO_ERROR; 845 } 846 847 /* Transaction content needs to be carefully prefixed to virtually 848 eliminate any chance for conflicts. The (repo, txn_id) pair 849 should be unique but if the filesystem format doesn't store the 850 global transaction ID via the txn-current file, and a transaction 851 fails, it might be possible to start a new transaction later that 852 receives the same id. For such older formats, throw in an uuid as 853 well -- just to be sure. */ 854 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 855 prefix = apr_pstrcat(pool, 856 "fsfs:", fs->uuid, 857 "/", fs->path, 858 ":", txn_id, 859 ":", "TXNDIR", 860 SVN_VA_NULL); 861 else 862 prefix = apr_pstrcat(pool, 863 "fsfs:", fs->uuid, 864 "/", fs->path, 865 ":", txn_id, 866 ":", svn_uuid_generate(pool), 867 ":", "TXNDIR", 868 SVN_VA_NULL); 869 870 /* create a txn-local directory cache */ 871 SVN_ERR(create_cache(&ffd->txn_dir_cache, 872 NULL, 873 svn_cache__get_global_membuffer_cache(), 874 1024, 8, 875 svn_fs_fs__serialize_txndir_entries, 876 svn_fs_fs__deserialize_dir_entries, 877 APR_HASH_KEY_STRING, 878 prefix, 879 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY, 880 TRUE, /* The TXN-ID is our namespace. */ 881 fs, 882 TRUE, 883 pool, pool)); 884 885 /* reset the transaction-specific cache if the pool gets cleaned up. */ 886 init_txn_callbacks(fs, &(ffd->txn_dir_cache), pool); 887 888 return SVN_NO_ERROR; 889} 890 891void 892svn_fs_fs__reset_txn_caches(svn_fs_t *fs) 893{ 894 /* we can always just reset the caches. This may degrade performance but 895 * can never cause in incorrect behavior. */ 896 897 fs_fs_data_t *ffd = fs->fsap_data; 898 ffd->txn_dir_cache = NULL; 899} 900