| /* caching.c : in-memory caching |
| * |
| * ==================================================================== |
| * Copyright (c) 2008 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| #include "fs.h" |
| #include "fs_fs.h" |
| #include "id.h" |
| #include "dag.h" |
| #include "../libsvn_fs/fs-loader.h" |
| |
| #include "svn_config.h" |
| |
| #include "svn_private_config.h" |
| |
| /*** Dup/serialize/deserialize functions. ***/ |
| |
| |
| /** Caching SVN_FS_ID_T values. **/ |
| /* Implements svn_cache__dup_func_t */ |
| static svn_error_t * |
| dup_id(void **out, |
| void *in, |
| apr_pool_t *pool) |
| { |
| svn_fs_id_t *id = in; |
| *out = svn_fs_fs__id_copy(id, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implements svn_cache__serialize_func_t */ |
| static svn_error_t * |
| serialize_id(char **data, |
| apr_size_t *data_len, |
| void *in, |
| apr_pool_t *pool) |
| { |
| svn_fs_id_t *id = in; |
| svn_string_t *id_str = svn_fs_fs__id_unparse(id, pool); |
| *data = (char *) id_str->data; |
| *data_len = id_str->len; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Implements svn_cache__deserialize_func_t */ |
| static svn_error_t * |
| deserialize_id(void **out, |
| const char *data, |
| apr_size_t data_len, |
| apr_pool_t *pool) |
| { |
| svn_fs_id_t *id = svn_fs_fs__id_parse(data, data_len, pool); |
| if (id == NULL) |
| { |
| return svn_error_create(SVN_ERR_FS_NOT_ID, NULL, |
| _("Bad ID in cache")); |
| } |
| |
| *out = id; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /** Caching directory listings. **/ |
| /* Implements svn_cache__dup_func_t */ |
| static svn_error_t * |
| dup_dir_listing(void **out, |
| void *in, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *new_entries = apr_hash_make(pool), *entries = in; |
| apr_hash_index_t *hi; |
| |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| void *val; |
| svn_fs_dirent_t *dirent, *new_dirent; |
| |
| apr_hash_this(hi, NULL, NULL, &val); |
| dirent = val; |
| new_dirent = apr_palloc(pool, sizeof(*new_dirent)); |
| new_dirent->name = apr_pstrdup(pool, dirent->name); |
| new_dirent->kind = dirent->kind; |
| new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool); |
| apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING, |
| new_dirent); |
| } |
| |
| *out = new_entries; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /** Caching packed rev offsets. **/ |
| /* Implements svn_cache__serialize_func_t */ |
| static svn_error_t * |
| manifest_serialize(char **data, |
| apr_size_t *data_len, |
| void *in, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *manifest = in; |
| |
| *data_len = sizeof(apr_off_t) *manifest->nelts; |
| *data = apr_palloc(pool, *data_len); |
| memcpy(*data, manifest->elts, *data_len); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implements svn_cache__deserialize_func_t */ |
| static svn_error_t * |
| manifest_deserialize(void **out, |
| const char *data, |
| apr_size_t data_len, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *manifest = apr_array_make(pool, |
| data_len / sizeof(apr_off_t), |
| sizeof(apr_off_t)); |
| memcpy(manifest->elts, data, data_len); |
| manifest->nelts = data_len / sizeof(apr_off_t); |
| *out = manifest; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implements svn_cache__dup_func_t */ |
| static svn_error_t * |
| dup_pack_manifest(void **out, |
| void *in, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *manifest = in; |
| |
| *out = apr_array_copy(pool, manifest); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Return a memcache in *MEMCACHE_P for FS if it's configured to use |
| memcached, or NULL otherwise. Also, sets *FAIL_STOP to a boolean |
| indicating whether cache errors should be returned to the caller or |
| just passed to the FS warning handler. Use FS->pool for allocating |
| the memcache, and POOL for temporary allocations. */ |
| static svn_error_t * |
| read_config(svn_memcache_t **memcache_p, |
| svn_boolean_t *fail_stop, |
| svn_fs_t *fs, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| |
| SVN_ERR(svn_cache__make_memcache_from_config(memcache_p, ffd->config, |
| fs->pool)); |
| return svn_config_get_bool(ffd->config, fail_stop, |
| CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP, |
| FALSE); |
| } |
| |
| |
| /* Implements svn_cache__error_handler_t */ |
| static svn_error_t * |
| warn_on_cache_errors(svn_error_t *err, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs = baton; |
| (fs->warning)(fs->warning_baton, err); |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_fs_fs__initialize_caches(svn_fs_t *fs, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| const char *prefix = apr_pstrcat(pool, |
| "fsfs:", ffd->uuid, |
| "/", fs->path, ":", |
| NULL); |
| svn_memcache_t *memcache; |
| svn_boolean_t no_handler; |
| |
| SVN_ERR(read_config(&memcache, &no_handler, fs, pool)); |
| |
| /* Make the cache for revision roots. For the vast majority of |
| * commands, this is only going to contain a few entries (svnadmin |
| * dump/verify is an exception here), so to reduce overhead let's |
| * try to keep it to just one page. I estimate each entry has about |
| * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t + |
| * id_private_t + 3 strings for value, and the cache_entry); the |
| * default pool size is 8192, so about a hundred should fit |
| * comfortably. */ |
| if (memcache) |
| SVN_ERR(svn_cache__create_memcache(&(ffd->rev_root_id_cache), |
| memcache, |
| serialize_id, |
| deserialize_id, |
| sizeof(svn_revnum_t), |
| apr_pstrcat(pool, prefix, "RRI", |
| NULL), |
| fs->pool)); |
| else |
| SVN_ERR(svn_cache__create_inprocess(&(ffd->rev_root_id_cache), |
| dup_id, sizeof(svn_revnum_t), |
| 1, 100, FALSE, fs->pool)); |
| if (! no_handler) |
| SVN_ERR(svn_cache__set_error_handler(ffd->rev_root_id_cache, |
| warn_on_cache_errors, fs, pool)); |
| |
| |
| /* Rough estimate: revision DAG nodes have size around 320 bytes, so |
| * let's put 16 on a page. */ |
| if (memcache) |
| SVN_ERR(svn_cache__create_memcache(&(ffd->rev_node_cache), |
| memcache, |
| svn_fs_fs__dag_serialize, |
| svn_fs_fs__dag_deserialize, |
| APR_HASH_KEY_STRING, |
| apr_pstrcat(pool, prefix, "DAG", |
| NULL), |
| fs->pool)); |
| else |
| SVN_ERR(svn_cache__create_inprocess(&(ffd->rev_node_cache), |
| svn_fs_fs__dag_dup_for_cache, |
| APR_HASH_KEY_STRING, |
| 1024, 16, FALSE, fs->pool)); |
| if (! no_handler) |
| SVN_ERR(svn_cache__set_error_handler(ffd->rev_node_cache, |
| warn_on_cache_errors, fs, pool)); |
| |
| |
| /* Very rough estimate: 1K per directory. */ |
| if (memcache) |
| SVN_ERR(svn_cache__create_memcache(&(ffd->dir_cache), |
| memcache, |
| svn_fs_fs__dir_entries_serialize, |
| svn_fs_fs__dir_entries_deserialize, |
| APR_HASH_KEY_STRING, |
| apr_pstrcat(pool, prefix, "DIR", |
| NULL), |
| fs->pool)); |
| else |
| SVN_ERR(svn_cache__create_inprocess(&(ffd->dir_cache), |
| dup_dir_listing, APR_HASH_KEY_STRING, |
| 1024, 8, FALSE, fs->pool)); |
| |
| if (! no_handler) |
| SVN_ERR(svn_cache__set_error_handler(ffd->dir_cache, |
| warn_on_cache_errors, fs, pool)); |
| |
| /* Only 16 bytes per entry (a revision number + the corresponding offset). |
| Since we want ~8k pages, that means 512 entries per page. */ |
| if (memcache) |
| SVN_ERR(svn_cache__create_memcache(&(ffd->packed_offset_cache), |
| memcache, |
| manifest_serialize, |
| manifest_deserialize, |
| sizeof(svn_revnum_t), |
| apr_pstrcat(pool, prefix, "PACK-MANIFEST", |
| NULL), |
| fs->pool)); |
| else |
| SVN_ERR(svn_cache__create_inprocess(&(ffd->packed_offset_cache), |
| dup_pack_manifest, sizeof(svn_revnum_t), |
| 32, 1, FALSE, fs->pool)); |
| |
| if (! no_handler) |
| SVN_ERR(svn_cache__set_error_handler(ffd->packed_offset_cache, |
| warn_on_cache_errors, fs, pool)); |
| |
| if (memcache) |
| { |
| SVN_ERR(svn_cache__create_memcache(&(ffd->fulltext_cache), |
| memcache, |
| /* Values are svn_string_t */ |
| NULL, NULL, |
| APR_HASH_KEY_STRING, |
| apr_pstrcat(pool, prefix, "TEXT", |
| NULL), |
| fs->pool)); |
| if (! no_handler) |
| SVN_ERR(svn_cache__set_error_handler(ffd->fulltext_cache, |
| warn_on_cache_errors, fs, pool)); |
| } |
| else |
| ffd->fulltext_cache = NULL; |
| |
| return SVN_NO_ERROR; |
| } |