]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - subversion/libsvn_fs_fs/caching.c
Import Subversion-1.10.0
[FreeBSD/FreeBSD.git] / subversion / libsvn_fs_fs / caching.c
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  */
45 static const char *
46 normalize_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. */
75 static svn_error_t *
76 read_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  */
134 static svn_error_t *
135 warn_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  */
149 static svn_error_t *
150 warn_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, */
161 struct 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. */
172 static apr_status_t
173 dump_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
213 static apr_status_t
214 dump_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  */
243 static svn_error_t *
244 init_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  * */
292 static svn_error_t *
293 create_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
349 svn_error_t *
350 svn_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, */
731 struct 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. */
747 static apr_status_t
748 remove_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. */
752 static apr_status_t
753 remove_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. */
774 static apr_status_t
775 remove_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  */
799 static void
800 init_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
829 svn_error_t *
830 svn_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
891 void
892 svn_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 }