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