]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_fs_fs/caching.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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 "temp_serializer.h"
29 #include "../libsvn_fs/fs-loader.h"
30
31 #include "svn_config.h"
32 #include "svn_cache_config.h"
33
34 #include "svn_private_config.h"
35 #include "svn_hash.h"
36 #include "svn_pools.h"
37
38 #include "private/svn_debug.h"
39 #include "private/svn_subr_private.h"
40
41 /* Take the ORIGINAL string and replace all occurrences of ":" without
42  * limiting the key space.  Allocate the result in POOL.
43  */
44 static const char *
45 normalize_key_part(const char *original,
46                    apr_pool_t *pool)
47 {
48   apr_size_t i;
49   apr_size_t len = strlen(original);
50   svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool);
51
52   for (i = 0; i < len; ++i)
53     {
54       char c = original[i];
55       switch (c)
56         {
57         case ':': svn_stringbuf_appendbytes(normalized, "%_", 2);
58                   break;
59         case '%': svn_stringbuf_appendbytes(normalized, "%%", 2);
60                   break;
61         default : svn_stringbuf_appendbyte(normalized, c);
62         }
63     }
64
65   return normalized->data;
66 }
67
68 /* Return a memcache in *MEMCACHE_P for FS if it's configured to use
69    memcached, or NULL otherwise.  Also, sets *FAIL_STOP to a boolean
70    indicating whether cache errors should be returned to the caller or
71    just passed to the FS warning handler.
72
73    *CACHE_TXDELTAS, *CACHE_FULLTEXTS and *CACHE_REVPROPS flags will be set
74    according to FS->CONFIG.  *CACHE_NAMESPACE receives the cache prefix
75    to use.
76
77    Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
78    for temporary allocations. */
79 static svn_error_t *
80 read_config(svn_memcache_t **memcache_p,
81             svn_boolean_t *fail_stop,
82             const char **cache_namespace,
83             svn_boolean_t *cache_txdeltas,
84             svn_boolean_t *cache_fulltexts,
85             svn_boolean_t *cache_revprops,
86             svn_fs_t *fs,
87             apr_pool_t *pool)
88 {
89   fs_fs_data_t *ffd = fs->fsap_data;
90
91   SVN_ERR(svn_cache__make_memcache_from_config(memcache_p, ffd->config,
92                                                fs->pool));
93
94   /* No cache namespace by default.  I.e. all FS instances share the
95    * cached data.  If you specify different namespaces, the data will
96    * share / compete for the same cache memory but keys will not match
97    * across namespaces and, thus, cached data will not be shared between
98    * namespaces.
99    *
100    * Since the namespace will be concatenated with other elements to form
101    * the complete key prefix, we must make sure that the resulting string
102    * is unique and cannot be created by any other combination of elements.
103    */
104   *cache_namespace
105     = normalize_key_part(svn_hash__get_cstring(fs->config,
106                                                SVN_FS_CONFIG_FSFS_CACHE_NS,
107                                                ""),
108                          pool);
109
110   /* don't cache text deltas by default.
111    * Once we reconstructed the fulltexts from the deltas,
112    * these deltas are rarely re-used. Therefore, only tools
113    * like svnadmin will activate this to speed up operations
114    * dump and verify.
115    */
116   *cache_txdeltas
117     = svn_hash__get_bool(fs->config,
118                          SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
119                          FALSE);
120   /* by default, cache fulltexts.
121    * Most SVN tools care about reconstructed file content.
122    * Thus, this is a reasonable default.
123    * SVN admin tools may set that to FALSE because fulltexts
124    * won't be re-used rendering the cache less effective
125    * by squeezing wanted data out.
126    */
127   *cache_fulltexts
128     = svn_hash__get_bool(fs->config,
129                          SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
130                          TRUE);
131
132   /* For now, always disable revprop caching.
133    */
134   *cache_revprops = FALSE;
135
136   return svn_config_get_bool(ffd->config, fail_stop,
137                              CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
138                              FALSE);
139 }
140
141
142 /* Implements svn_cache__error_handler_t
143  * This variant clears the error after logging it.
144  */
145 static svn_error_t *
146 warn_and_continue_on_cache_errors(svn_error_t *err,
147                                   void *baton,
148                                   apr_pool_t *pool)
149 {
150   svn_fs_t *fs = baton;
151   (fs->warning)(fs->warning_baton, err);
152   svn_error_clear(err);
153
154   return SVN_NO_ERROR;
155 }
156
157 /* Implements svn_cache__error_handler_t
158  * This variant logs the error and passes it on to the callers.
159  */
160 static svn_error_t *
161 warn_and_fail_on_cache_errors(svn_error_t *err,
162                               void *baton,
163                               apr_pool_t *pool)
164 {
165   svn_fs_t *fs = baton;
166   (fs->warning)(fs->warning_baton, err);
167   return err;
168 }
169
170 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
171 /* Baton to be used for the dump_cache_statistics() pool cleanup function, */
172 struct dump_cache_baton_t
173 {
174   /* the pool about to be cleaned up. Will be used for temp. allocations. */
175   apr_pool_t *pool;
176
177   /* the cache to dump the statistics for */
178   svn_cache__t *cache;
179 };
180
181 /* APR pool cleanup handler that will printf the statistics of the
182    cache referenced by the baton in BATON_VOID. */
183 static apr_status_t
184 dump_cache_statistics(void *baton_void)
185 {
186   struct dump_cache_baton_t *baton = baton_void;
187
188   apr_status_t result = APR_SUCCESS;
189   svn_cache__info_t info;
190   svn_string_t *text_stats;
191   apr_array_header_t *lines;
192   int i;
193
194   svn_error_t *err = svn_cache__get_info(baton->cache,
195                                          &info,
196                                          TRUE,
197                                          baton->pool);
198
199   if (! err)
200     {
201       text_stats = svn_cache__format_info(&info, baton->pool);
202       lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool);
203
204       for (i = 0; i < lines->nelts; ++i)
205         {
206           const char *line = APR_ARRAY_IDX(lines, i, const char *);
207 #ifdef SVN_DEBUG
208           SVN_DBG(("%s\n", line));
209 #endif
210         }
211     }
212
213   /* process error returns */
214   if (err)
215     {
216       result = err->apr_err;
217       svn_error_clear(err);
218     }
219
220   return result;
221 }
222 #endif /* SVN_DEBUG_CACHE_DUMP_STATS */
223
224 /* This function sets / registers the required callbacks for a given
225  * not transaction-specific CACHE object in FS, if CACHE is not NULL.
226  *
227  * All these svn_cache__t instances shall be handled uniformly. Unless
228  * ERROR_HANDLER is NULL, register it for the given CACHE in FS.
229  */
230 static svn_error_t *
231 init_callbacks(svn_cache__t *cache,
232                svn_fs_t *fs,
233                svn_cache__error_handler_t error_handler,
234                apr_pool_t *pool)
235 {
236   if (cache != NULL)
237     {
238 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
239
240       /* schedule printing the access statistics upon pool cleanup,
241        * i.e. end of FSFS session.
242        */
243       struct dump_cache_baton_t *baton;
244
245       baton = apr_palloc(pool, sizeof(*baton));
246       baton->pool = pool;
247       baton->cache = cache;
248
249       apr_pool_cleanup_register(pool,
250                                 baton,
251                                 dump_cache_statistics,
252                                 apr_pool_cleanup_null);
253 #endif
254
255       if (error_handler)
256         SVN_ERR(svn_cache__set_error_handler(cache,
257                                              error_handler,
258                                              fs,
259                                              pool));
260
261     }
262
263   return SVN_NO_ERROR;
264 }
265
266 /* Sets *CACHE_P to cache instance based on provided options.
267  * Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if
268  * MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
269  * MEMBUFFER are NULL and pages is non-zero.  Sets *CACHE_P to NULL
270  * otherwise.
271  *
272  * Unless NO_HANDLER is true, register an error handler that reports errors
273  * as warnings to the FS warning callback.
274  *
275  * Cache is allocated in POOL.
276  * */
277 static svn_error_t *
278 create_cache(svn_cache__t **cache_p,
279              svn_memcache_t *memcache,
280              svn_membuffer_t *membuffer,
281              apr_int64_t pages,
282              apr_int64_t items_per_page,
283              svn_cache__serialize_func_t serializer,
284              svn_cache__deserialize_func_t deserializer,
285              apr_ssize_t klen,
286              const char *prefix,
287              svn_fs_t *fs,
288              svn_boolean_t no_handler,
289              apr_pool_t *pool)
290 {
291   svn_cache__error_handler_t error_handler = no_handler
292                                            ? NULL
293                                            : warn_and_fail_on_cache_errors;
294
295   if (memcache)
296     {
297       SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
298                                          serializer, deserializer, klen,
299                                          prefix, pool));
300       error_handler = no_handler
301                     ? NULL
302                     : warn_and_continue_on_cache_errors;
303     }
304   else if (membuffer)
305     {
306       SVN_ERR(svn_cache__create_membuffer_cache(
307                 cache_p, membuffer, serializer, deserializer,
308                 klen, prefix, FALSE, pool));
309     }
310   else if (pages)
311     {
312       SVN_ERR(svn_cache__create_inprocess(
313                 cache_p, serializer, deserializer, klen, pages,
314                 items_per_page, FALSE, prefix, pool));
315     }
316   else
317     {
318       *cache_p = NULL;
319     }
320
321   SVN_ERR(init_callbacks(*cache_p, fs, error_handler, pool));
322
323   return SVN_NO_ERROR;
324 }
325
326 svn_error_t *
327 svn_fs_fs__initialize_caches(svn_fs_t *fs,
328                              apr_pool_t *pool)
329 {
330   fs_fs_data_t *ffd = fs->fsap_data;
331   const char *prefix = apr_pstrcat(pool,
332                                    "fsfs:", fs->uuid,
333                                    "/", normalize_key_part(fs->path, pool),
334                                    ":",
335                                    (char *)NULL);
336   svn_memcache_t *memcache;
337   svn_membuffer_t *membuffer;
338   svn_boolean_t no_handler;
339   svn_boolean_t cache_txdeltas;
340   svn_boolean_t cache_fulltexts;
341   svn_boolean_t cache_revprops;
342   const char *cache_namespace;
343
344   /* Evaluating the cache configuration. */
345   SVN_ERR(read_config(&memcache,
346                       &no_handler,
347                       &cache_namespace,
348                       &cache_txdeltas,
349                       &cache_fulltexts,
350                       &cache_revprops,
351                       fs,
352                       pool));
353
354   prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, NULL);
355
356   membuffer = svn_cache__get_global_membuffer_cache();
357
358   /* Make the cache for revision roots.  For the vast majority of
359    * commands, this is only going to contain a few entries (svnadmin
360    * dump/verify is an exception here), so to reduce overhead let's
361    * try to keep it to just one page.  I estimate each entry has about
362    * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t +
363    * id_private_t + 3 strings for value, and the cache_entry); the
364    * default pool size is 8192, so about a hundred should fit
365    * comfortably. */
366   SVN_ERR(create_cache(&(ffd->rev_root_id_cache),
367                        NULL,
368                        membuffer,
369                        1, 100,
370                        svn_fs_fs__serialize_id,
371                        svn_fs_fs__deserialize_id,
372                        sizeof(svn_revnum_t),
373                        apr_pstrcat(pool, prefix, "RRI", (char *)NULL),
374                        fs,
375                        no_handler,
376                        fs->pool));
377
378   /* Rough estimate: revision DAG nodes have size around 320 bytes, so
379    * let's put 16 on a page. */
380   SVN_ERR(create_cache(&(ffd->rev_node_cache),
381                        NULL,
382                        membuffer,
383                        1024, 16,
384                        svn_fs_fs__dag_serialize,
385                        svn_fs_fs__dag_deserialize,
386                        APR_HASH_KEY_STRING,
387                        apr_pstrcat(pool, prefix, "DAG", (char *)NULL),
388                        fs,
389                        no_handler,
390                        fs->pool));
391
392   /* 1st level DAG node cache */
393   ffd->dag_node_cache = svn_fs_fs__create_dag_cache(pool);
394
395   /* Very rough estimate: 1K per directory. */
396   SVN_ERR(create_cache(&(ffd->dir_cache),
397                        NULL,
398                        membuffer,
399                        1024, 8,
400                        svn_fs_fs__serialize_dir_entries,
401                        svn_fs_fs__deserialize_dir_entries,
402                        APR_HASH_KEY_STRING,
403                        apr_pstrcat(pool, prefix, "DIR", (char *)NULL),
404                        fs,
405                        no_handler,
406                        fs->pool));
407
408   /* Only 16 bytes per entry (a revision number + the corresponding offset).
409      Since we want ~8k pages, that means 512 entries per page. */
410   SVN_ERR(create_cache(&(ffd->packed_offset_cache),
411                        NULL,
412                        membuffer,
413                        32, 1,
414                        svn_fs_fs__serialize_manifest,
415                        svn_fs_fs__deserialize_manifest,
416                        sizeof(svn_revnum_t),
417                        apr_pstrcat(pool, prefix, "PACK-MANIFEST",
418                                    (char *)NULL),
419                        fs,
420                        no_handler,
421                        fs->pool));
422
423   /* initialize node revision cache, if caching has been enabled */
424   SVN_ERR(create_cache(&(ffd->node_revision_cache),
425                        NULL,
426                        membuffer,
427                        0, 0, /* Do not use inprocess cache */
428                        svn_fs_fs__serialize_node_revision,
429                        svn_fs_fs__deserialize_node_revision,
430                        sizeof(pair_cache_key_t),
431                        apr_pstrcat(pool, prefix, "NODEREVS", (char *)NULL),
432                        fs,
433                        no_handler,
434                        fs->pool));
435
436   /* initialize node change list cache, if caching has been enabled */
437   SVN_ERR(create_cache(&(ffd->changes_cache),
438                        NULL,
439                        membuffer,
440                        0, 0, /* Do not use inprocess cache */
441                        svn_fs_fs__serialize_changes,
442                        svn_fs_fs__deserialize_changes,
443                        sizeof(svn_revnum_t),
444                        apr_pstrcat(pool, prefix, "CHANGES", (char *)NULL),
445                        fs,
446                        no_handler,
447                        fs->pool));
448
449   /* if enabled, cache fulltext and other derived information */
450   if (cache_fulltexts)
451     {
452       SVN_ERR(create_cache(&(ffd->fulltext_cache),
453                            memcache,
454                            membuffer,
455                            0, 0, /* Do not use inprocess cache */
456                            /* Values are svn_stringbuf_t */
457                            NULL, NULL,
458                            sizeof(pair_cache_key_t),
459                            apr_pstrcat(pool, prefix, "TEXT", (char *)NULL),
460                            fs,
461                            no_handler,
462                            fs->pool));
463
464       SVN_ERR(create_cache(&(ffd->properties_cache),
465                            NULL,
466                            membuffer,
467                            0, 0, /* Do not use inprocess cache */
468                            svn_fs_fs__serialize_properties,
469                            svn_fs_fs__deserialize_properties,
470                            sizeof(pair_cache_key_t),
471                            apr_pstrcat(pool, prefix, "PROP",
472                                        (char *)NULL),
473                            fs,
474                            no_handler,
475                            fs->pool));
476
477       SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
478                            NULL,
479                            membuffer,
480                            0, 0, /* Do not use inprocess cache */
481                            svn_fs_fs__serialize_mergeinfo,
482                            svn_fs_fs__deserialize_mergeinfo,
483                            APR_HASH_KEY_STRING,
484                            apr_pstrcat(pool, prefix, "MERGEINFO",
485                                        (char *)NULL),
486                            fs,
487                            no_handler,
488                            fs->pool));
489
490       SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
491                            NULL,
492                            membuffer,
493                            0, 0, /* Do not use inprocess cache */
494                            /* Values are svn_stringbuf_t */
495                            NULL, NULL,
496                            APR_HASH_KEY_STRING,
497                            apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
498                                        (char *)NULL),
499                            fs,
500                            no_handler,
501                            fs->pool));
502     }
503   else
504     {
505       ffd->fulltext_cache = NULL;
506       ffd->properties_cache = NULL;
507       ffd->mergeinfo_cache = NULL;
508       ffd->mergeinfo_existence_cache = NULL;
509     }
510
511   /* initialize revprop cache, if full-text caching has been enabled */
512   if (cache_revprops)
513     {
514       SVN_ERR(create_cache(&(ffd->revprop_cache),
515                            NULL,
516                            membuffer,
517                            0, 0, /* Do not use inprocess cache */
518                            svn_fs_fs__serialize_properties,
519                            svn_fs_fs__deserialize_properties,
520                            sizeof(pair_cache_key_t),
521                            apr_pstrcat(pool, prefix, "REVPROP",
522                                        (char *)NULL),
523                            fs,
524                            no_handler,
525                            fs->pool));
526     }
527   else
528     {
529       ffd->revprop_cache = NULL;
530     }
531
532   /* if enabled, cache text deltas and their combinations */
533   if (cache_txdeltas)
534     {
535       SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
536                            NULL,
537                            membuffer,
538                            0, 0, /* Do not use inprocess cache */
539                            svn_fs_fs__serialize_txdelta_window,
540                            svn_fs_fs__deserialize_txdelta_window,
541                            APR_HASH_KEY_STRING,
542                            apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
543                                        (char *)NULL),
544                            fs,
545                            no_handler,
546                            fs->pool));
547
548       SVN_ERR(create_cache(&(ffd->combined_window_cache),
549                            NULL,
550                            membuffer,
551                            0, 0, /* Do not use inprocess cache */
552                            /* Values are svn_stringbuf_t */
553                            NULL, NULL,
554                            APR_HASH_KEY_STRING,
555                            apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
556                                        (char *)NULL),
557                            fs,
558                            no_handler,
559                            fs->pool));
560     }
561   else
562     {
563       ffd->txdelta_window_cache = NULL;
564       ffd->combined_window_cache = NULL;
565     }
566
567   return SVN_NO_ERROR;
568 }
569
570 /* Baton to be used for the remove_txn_cache() pool cleanup function, */
571 struct txn_cleanup_baton_t
572 {
573   /* the cache to reset */
574   svn_cache__t *txn_cache;
575
576   /* the position where to reset it */
577   svn_cache__t **to_reset;
578 };
579
580 /* APR pool cleanup handler that will reset the cache pointer given in
581    BATON_VOID. */
582 static apr_status_t
583 remove_txn_cache(void *baton_void)
584 {
585   struct txn_cleanup_baton_t *baton = baton_void;
586
587   /* be careful not to hurt performance by resetting newer txn's caches. */
588   if (*baton->to_reset == baton->txn_cache)
589     {
590      /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
591       *baton->to_reset  = NULL;
592     }
593
594   return  APR_SUCCESS;
595 }
596
597 /* This function sets / registers the required callbacks for a given
598  * transaction-specific *CACHE object, if CACHE is not NULL and a no-op
599  * otherwise. In particular, it will ensure that *CACHE gets reset to NULL
600  * upon POOL destruction latest.
601  */
602 static void
603 init_txn_callbacks(svn_cache__t **cache,
604                    apr_pool_t *pool)
605 {
606   if (*cache != NULL)
607     {
608       struct txn_cleanup_baton_t *baton;
609
610       baton = apr_palloc(pool, sizeof(*baton));
611       baton->txn_cache = *cache;
612       baton->to_reset = cache;
613
614       apr_pool_cleanup_register(pool,
615                                 baton,
616                                 remove_txn_cache,
617                                 apr_pool_cleanup_null);
618     }
619 }
620
621 svn_error_t *
622 svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
623                                  const char *txn_id,
624                                  apr_pool_t *pool)
625 {
626   fs_fs_data_t *ffd = fs->fsap_data;
627
628   /* Transaction content needs to be carefully prefixed to virtually
629      eliminate any chance for conflicts. The (repo, txn_id) pair
630      should be unique but if a transaction fails, it might be possible
631      to start a new transaction later that receives the same id.
632      Therefore, throw in a uuid as well - just to be sure. */
633   const char *prefix = apr_pstrcat(pool,
634                                    "fsfs:", fs->uuid,
635                                    "/", fs->path,
636                                    ":", txn_id,
637                                    ":", svn_uuid_generate(pool), ":",
638                                    (char *)NULL);
639
640   /* We don't support caching for concurrent transactions in the SAME
641    * FSFS session. Maybe, you forgot to clean POOL. */
642   if (ffd->txn_dir_cache != NULL || ffd->concurrent_transactions)
643     {
644       ffd->txn_dir_cache = NULL;
645       ffd->concurrent_transactions = TRUE;
646
647       return SVN_NO_ERROR;
648     }
649
650   /* create a txn-local directory cache */
651   SVN_ERR(create_cache(&ffd->txn_dir_cache,
652                        NULL,
653                        svn_cache__get_global_membuffer_cache(),
654                        1024, 8,
655                        svn_fs_fs__serialize_dir_entries,
656                        svn_fs_fs__deserialize_dir_entries,
657                        APR_HASH_KEY_STRING,
658                        apr_pstrcat(pool, prefix, "TXNDIR",
659                                    (char *)NULL),
660                        fs,
661                        TRUE,
662                        pool));
663
664   /* reset the transaction-specific cache if the pool gets cleaned up. */
665   init_txn_callbacks(&(ffd->txn_dir_cache), pool);
666
667   return SVN_NO_ERROR;
668 }
669
670 void
671 svn_fs_fs__reset_txn_caches(svn_fs_t *fs)
672 {
673   /* we can always just reset the caches. This may degrade performance but
674    * can never cause in incorrect behavior. */
675
676   fs_fs_data_t *ffd = fs->fsap_data;
677   ffd->txn_dir_cache = NULL;
678 }