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