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