]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/caching.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / 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 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. */
74 static svn_error_t *
75 read_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  */
127 static svn_error_t *
128 warn_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  */
142 static svn_error_t *
143 warn_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, */
154 struct 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. */
165 static apr_status_t
166 dump_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
206 static apr_status_t
207 dump_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  */
236 static svn_error_t *
237 init_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  * */
284 static svn_error_t *
285 create_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
337 svn_error_t *
338 svn_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_DEFAULT_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, */
672 struct 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. */
688 static apr_status_t
689 remove_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. */
693 static apr_status_t
694 remove_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. */
715 static apr_status_t
716 remove_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  */
740 static void
741 init_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
770 svn_error_t *
771 svn_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                        0,
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
820 void
821 svn_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 }