Author: stefan2 Date: Sat Jul 31 20:46:59 2010 New Revision: 981091 URL: http://svn.apache.org/viewvc?rev=981091&view=rev Log: Introduce a private file handle cache API and provide an implementation.
* subversion/include/private/svn_file_cache.h (svn_file_cache_t, svn_file_cache__handle_t): introduce new opaque data types (svn_file_cache__open, svn_file_cache__has_file, svn_file_cache__get_apr_handle, svn_file_cache__get_name, svn_file_cache__close, svn_file_cache__flush, svn_file_cache__create_cache): declare new private API functions * subversion/libsvn_subr/svn_file_cache.c (cache_entry_t, entry_link_t, entry_list_t): define new internal data structures (svn_file_cache_t, svn_file_cache__handle_t): define data types used in the API (lock_cache, unlock_cache, init_list, init_link, link_link, unlink_link, get_previous_entry, get_next_entry, append_to_list, remove_from_list, find_first, auto_close_cached_handle, internal_file_open, internal_close_file, close_handle_before_cleanup, open_entry, close_oldest_idle, auto_close_oldest, pointer_is_closer): new utility functions (svn_file_cache__open, svn_file_cache__has_file, svn_file_cache__get_apr_handle, svn_file_cache__get_name, svn_file_cache__close, svn_file_cache__flush, svn_file_cache__create_cache): implement new private API functions Added: subversion/branches/performance/subversion/include/private/svn_file_cache.h (with props) subversion/branches/performance/subversion/libsvn_subr/svn_file_cache.c (with props) Added: subversion/branches/performance/subversion/include/private/svn_file_cache.h URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/include/private/svn_file_cache.h?rev=981091&view=auto ============================================================================== --- subversion/branches/performance/subversion/include/private/svn_file_cache.h (added) +++ subversion/branches/performance/subversion/include/private/svn_file_cache.h Sat Jul 31 20:46:59 2010 @@ -0,0 +1,114 @@ +/** + * @copyright + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * @endcopyright + * + * @file svn_file_cache.h + * @brief File handle cache API + */ + +#include <apr_file_io.h> +#include "svn_types.h" + +/** + * An opaque structure representing a cache for open file handles. + */ +typedef struct svn_file_cache_t svn_file_cache_t; + +/** + * An opaque structure representing a cached file handle being used + * by the calling application. + */ +typedef struct svn_file_cache__handle_t svn_file_cache__handle_t; + +/** + * Get an open file handle in @a f, for the file named @a fname with the + * open flag(s) in @a flag and permissions in @a perm. These parameters + * are the same as in @ref svn_io_file_open. The file pointer will be + * moved to the specified @a offset, if it is different from -1. + * + * If there are one or more unused matching open file handles, those with + * the specified @a cookie will be preferred. This is particularly useful + * if @a offset is -1, i.e. if the file pointer position of the handle + * returned is undefined. + */ +svn_error_t * +svn_file_cache__open(svn_file_cache__handle_t **f, + svn_file_cache_t *cache, + const char *fname, + apr_int32_t flag, + apr_fileperms_t perm, + apr_off_t offset, + int cookie, + apr_pool_t *pool); + +/** + * Efficiently check whether the file handle cache @a cache holds an open + * handle to the file named @a fname. This is basically an efficient way + * to check that a file exists. However, a @c FALSE result does not mean + * that the respective file does not exist. + */ +svn_boolean_t +svn_file_cache__has_file(svn_file_cache_t *cache, + const char *fname); + +/** + * Return the APR level file handle underlying the cache file handle @a f. + * Returns NULL, if @a f is NULL, has already been closed or otherwise + * invalidated. + */ +apr_file_t * +svn_file_cache__get_apr_handle(svn_file_cache__handle_t *f); + +/** + * Return the name of the file that the cached handle @a f refers to. + * Returns NULL, if @a f is NULL, has already been closed or otherwise + * invalidated. + */ +const char * +svn_file_cache__get_name(svn_file_cache__handle_t *f); + +/** + * Return the cached file handle @a f to the cache. Depending on the number + * of open handles, the underlying handle may actually get closed. If @a f + * is NULL, already closed or an invalidated handle, this is a no-op. + */ +svn_error_t * +svn_file_cache__close(svn_file_cache__handle_t *f); + +/** + * Close all file handles currently not held by the application. + */ +svn_error_t * +svn_file_cache__flush(svn_file_cache_t *cache); + +/** + * Creates a new file handle cache in @a cache. Up to @a max_handles + * file handles will be kept open. All cache-internal memory allocations + * during the caches lifetime will be done from @a pool. + * + * If the caller ensures that there are no concurrent accesses to the + * cache, @a thread_safe may be @c FALSE. Otherwise, it must be @c TRUE. + */ +svn_error_t * +svn_file_cache__create_cache(svn_file_cache_t **cache, + size_t max_handles, + svn_boolean_t thread_safe, + apr_pool_t *pool); Propchange: subversion/branches/performance/subversion/include/private/svn_file_cache.h ------------------------------------------------------------------------------ svn:eol-style = native Added: subversion/branches/performance/subversion/libsvn_subr/svn_file_cache.c URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_subr/svn_file_cache.c?rev=981091&view=auto ============================================================================== --- subversion/branches/performance/subversion/libsvn_subr/svn_file_cache.c (added) +++ subversion/branches/performance/subversion/libsvn_subr/svn_file_cache.c Sat Jul 31 20:46:59 2010 @@ -0,0 +1,910 @@ +/* + * svn_file_cache.c: open file handle caching for Subversion + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <assert.h> + +#include <apr_thread_mutex.h> + +#include "private/svn_file_cache.h" +#include "svn_private_config.h" +#include "svn_pools.h" +#include "svn_io.h" + +/* Subversion using FSFS often opens files for a only a short number of + * accesses. Since many revisions are stored in a single file, it is + * often a relatively small number of files that gets gets opened and + * closed again and again. + * + * That results in a high OS overhead for access control and handle setup. + * Furthermore, buffered file access results in the same file sections to + * be read repeatedly as well as in reading significantly more data than + * what will actually be processed. + * + * The file cache is meant to basically keep files open and handing them + * out to the application again and again. In that case, it is a simple + * facade to the APR file function. However, the cached file handles use + * a specialized data structure that allows for determining whether a + * given handle has already been returned to the cache or invalidated by + * destroying the cache object itself. + * + * Once opened, the APR file handles are kept in the cache even if they + * are "idle", i.e. not currently held by the application. A LRU schme + * will limit the number of open handles. + * + * Any file may be opened multiple times and the cache will try to hand out + * the handle that probably has the lowest access overhead. To that end, + * the current file pointer gets (optionally) compared with the location + * of the next access. If a cached file is found that may already have the + * desired data in its buffer, that handle will be returned instead of a + * random one. When the position of the next file access is not known, + * a cookie may be used to discern files in a similar way. + * + * For read-after-write scenarios, it is imperative to flush the APR file + * buffer before attempting to read that file. Therefore, all idle handles + * for that file should be closed before opening a file with different + * parameters. Because buffering may have affects on EOF detection etc. + * without the application being aware of it, no distinction is being made + * between read-after-write, write-after-read or others. + * + * For similar reasons, an application may want to close all idle handles + * explicitly, i.e. without opening new ones. svn_file_cache__flush is + * the function to call in that case. + */ + +/* forward-declarations */ +typedef struct cache_entry_t cache_entry_t; +typedef struct entry_link_t entry_link_t; +typedef struct entry_list_t entry_list_t; + +/* Element in a double-linked list. + */ +struct entry_link_t +{ + /* pointer to the actual data; must not be NULL */ + cache_entry_t *item; + + /* pointer to the next list element. NULL for the last element */ + entry_link_t *next; + + /* pointer to the previous list element. NULL for the first element */ + entry_link_t *previous; +}; + +/* Header of a double-linked list. For empty lists, all elements + * must be NULL. Otherwise, none must be NULL. + */ +struct entry_list_t +{ + /* pointer to the first element of list */ + entry_link_t *first; + + /* pointer to the last element of list */ + entry_link_t *last; + + /* number of elements in the list */ + size_t count; +}; + +/* A cache entry. It represents a single file handle. Since APR buffered + * files consume several kB of memory, we keep a private pool instance. + * We keep the entry objects around even after closing the file handle + * so we can reuse the memory by clearing the pool. + * + * The cache entry is linked in three lists: + * - the global list of either used or unused entry (having no file handle) + * - list of sibblings, i.e. file handles to the same file + * - global LRU list of idle file handles, i.e. those that are currently + * not used by the application + * + * The list elements (links) are members of this structure instead of + * heap-allocated objects. + */ +struct cache_entry_t +{ + /* mainly used to allocate the file name and handle */ + apr_pool_t *pool; + + /* the open file handle. If NULL, this is an unused (recyclable) entry */ + apr_file_t *file; + + /* The cached file handle object handed out to the application. + * If this is NULL, the entry is either idle or unused. */ + svn_file_cache__handle_t *open_handle; + + /* the file name. NULL for unused entries */ + const char *name; + + /* file open flag(s). Valid only for used entries. */ + apr_int32_t flag; + + /* granted file permissions. Valid only for used entries. */ + apr_fileperms_t perm; + + /* granted file permissions. Valid only for used entries. */ + int cookie; + + /* position of the file pointer. Valid only for idle entries. */ + apr_off_t position; + + /* link to the either the global list of used or unused entries + * (file_handle_cache_t.used_entries, file_handle_cache_t.unused_entries, + * respectively). */ + entry_link_t global_link; + + /* link to other used entries for the same file */ + entry_link_t sibling_link; + + /* link to the global LRU list of idle entries. + * Valid only for idle entries. */ + entry_link_t idle_link; +}; + +/* The file handle cache structure. + */ +struct svn_file_cache_t +{ + /* all cache sub-structures are allocated from this pool */ + apr_pool_t *pool; + + /* a limit to the number of APR file handles. It can be exceeded only by + * the application actually opening more cached file handles. Otherwise, + * idle entries will be closed as soon as the limit has been reached. */ + size_t max_used_count; + + /* list of recyclable entries, currently not holding an APR file handle. */ + entry_list_t unused_entries; + + /* list of entries holding an (open) APR file handle. */ + entry_list_t used_entries; + + /* subset of used_entries, containing all entries not currently in use + * by the application. */ + entry_list_t idle_entries; + + /* A handle index, mapping the file name to at most *one* used entry. + * The respective other entries for the same file name are then found + * by following the cache_entry_t.sibling_link list. */ + apr_hash_t *first_by_name; + +#if APR_HAS_THREADS + /* A lock for intra-process synchronization to the cache, or NULL if + * the cache's creator doesn't feel the cache needs to be + * thread-safe. */ + apr_thread_mutex_t *mutex; +#endif +}; + +/* Internal structure behind the opaque "cached file handle" returned to + * the application when it opens a file. Both members may be NULL, if + * either the handle has already been returned to the cache or the cache + * itself has been destroyed already. + */ +struct svn_file_cache__handle_t +{ + /* the issuing cache. Having that element here simplifies function + * signatures dealing with cached file handles. It also makes them + * harder to use incorrectly. */ + svn_file_cache_t *cache; + + /* the handle-specific information */ + cache_entry_t *entry; +}; + +/* If applicable, locks CACHE's mutex. + */ +static svn_error_t * +lock_cache(svn_file_cache_t *cache) +{ +#if APR_HAS_THREADS + apr_status_t status; + if (! cache->mutex) + return SVN_NO_ERROR; + + status = apr_thread_mutex_lock(cache->mutex); + if (status) + return svn_error_wrap_apr(status, _("Can't lock cache mutex")); +#endif + + return SVN_NO_ERROR; +} + +/* If applicable, unlocks CACHE's mutex, then returns ERR. + */ +static svn_error_t * +unlock_cache(svn_file_cache_t *cache, svn_error_t *err) +{ +#if APR_HAS_THREADS + apr_status_t status; + if (! cache->mutex) + return err; + + status = apr_thread_mutex_unlock(cache->mutex); + if (status && !err) + return svn_error_wrap_apr(status, _("Can't unlock cache mutex")); +#endif + + return err; +} + +/* Initialize LIST as empty. + */ +static void +init_list(entry_list_t *list) +{ + list->first = NULL; + list->last = NULL; + list->count = 0; +} + +/* Initialize a list element LINK and connect it to the data item ENTRY. + */ +static void +init_link(entry_link_t *link, cache_entry_t *entry) +{ + link->item = entry; + link->previous = NULL; + link->next = NULL; +} + +/* Insert element LINK into the a list just after PREVIOUS. + * None may be NULL. This function does *not* update the link header. + */ +static void +link_link(entry_link_t *link, entry_link_t *previous) +{ + /* link with next item, if that exists + */ + if (previous->next) + { + previous->next->previous = link; + link->next = previous->next; + } + + /* link with previous item + */ + previous->next = link; + link->previous = previous; +} + +/* Drop the element LINK from the list. + * This function does *not* update the link header. + */ +static void +unlink_link(entry_link_t *link) +{ + if (link->previous) + link->previous->next = link->next; + if (link->next) + link->next->previous = link->previous; + + link->next = NULL; + link->previous = NULL; +} + +/* Return the entry referenced from the previous element in the list. + * Returns NULL, if LINK is the list head. + */ +static APR_INLINE cache_entry_t * +get_previous_entry(entry_link_t *link) +{ + return link->previous ? link->previous->item : NULL; +} + +/* Return the entry referenced from the next element in the list. + * Returns NULL, if LINK is the last element in the list. + */ +static APR_INLINE cache_entry_t * +get_next_entry(entry_link_t *link) +{ + return link->next ? link->next->item : NULL; +} + +/* Append list element LINK to the LIST. + * LINK must not already be an element of any list. + */ +static void +append_to_list(entry_list_t *list, entry_link_t *link) +{ + if (list->last) + link_link(link, list->last); + else + list->first = link; + + list->last = link; + list->count++; +} + +/* Remove list element LINK from the LIST. + * LINK must actually be an element of LIST. + */ +static void +remove_from_list(entry_list_t *list, entry_link_t *link) +{ + list->count--; + + if (list->first == link) + list->first = link->next; + if (list->last == link) + list->last = link->previous; + + unlink_link(link); +} + +/* Returns the first CACHE entry for the given file NAME. + * If no such entry exists, the result is NULL. + */ +static cache_entry_t * +find_first(svn_file_cache_t *cache, const char *name) +{ + cache_entry_t *result = + (cache_entry_t *)apr_hash_get(cache->first_by_name, + name, + APR_HASH_KEY_STRING); + + /* the index must contain only used entries, i.e. those that actually + * contain an open APR file handle. */ + assert (!result || result->file); + return result; +} + +/* "Destructor" (APR pool item cleanup code) for cache entries. + * It ensures that cached file handles currently held by the application + * will be invalidated properly when the cache is destroyed, for instance. + */ +static apr_status_t +auto_close_cached_handle(void *entry_void) +{ + cache_entry_t *entry = entry_void; + if (entry->open_handle) + { + /* There is a cached file handle held by the application. Reset its + * internal pointers so it won't try to call cache functions. + */ + entry->open_handle->cache = NULL; + entry->open_handle->entry = NULL; + entry->open_handle = NULL; + } + + return APR_SUCCESS; +} + +/* Create a new APR-level file handle with the specified file NAME, FLAG + * and permissions PERM. A COOKIE will be attached to it. The corresponding + * cache item will be returned in RESULT. + */ +static svn_error_t * +internal_file_open(cache_entry_t **result, + svn_file_cache_t *cache, + const char *name, + apr_int32_t flag, + apr_fileperms_t perm, + int cookie) +{ + cache_entry_t *entry; + cache_entry_t *sibling; + + /* Can we recycle an existing, currently unused, cache entry? + */ + if (cache->unused_entries.first) + { + /* yes, extract it from the "unused" list + */ + entry = cache->unused_entries.first->item; + remove_from_list(&cache->unused_entries, &entry->global_link); + } + else + { + /* no, create a new entry and initialize it (except for the file info) + */ + entry = apr_palloc(cache->pool, sizeof(cache_entry_t)); + entry->file = NULL; + entry->open_handle = NULL; + entry->pool = svn_pool_create(cache->pool); + + init_link(&entry->global_link, entry); + init_link(&entry->sibling_link, entry); + init_link(&entry->idle_link, entry); + } + + /* (try to) open the requested file */ + SVN_ERR(svn_io_file_open(&entry->file, name, flag, perm, entry->pool)); + assert(entry->file); + + /* make sure we auto-close cached file handles held by the application + * before actually closing the file. */ + apr_pool_cleanup_register(entry->pool, + entry, + auto_close_cached_handle, + apr_pool_cleanup_null); + + /* set file info */ + entry->flag = flag; + entry->perm = perm; + entry->cookie = cookie; + entry->name = apr_pstrdup(entry->pool, name); + entry->position = 0; + + /* This cache entry is now "used" (has an APR file handle) and "idle" + * (not held by the application, yet). + */ + append_to_list(&cache->used_entries, &entry->global_link); + append_to_list(&cache->idle_entries, &entry->idle_link); + + /* link with other entries for the same file in the index, or add it + * to the index if no entry for this file name exists so far */ + sibling = find_first(cache, name); + if (sibling) + link_link(&entry->sibling_link, &sibling->sibling_link); + else + apr_hash_set(cache->first_by_name, + entry->name, + APR_HASH_KEY_STRING, + entry); + + /* done */ + *result = entry; + + return SVN_NO_ERROR; +} + +/* actually close the underlying APR file handle in the ENTRY. + * The entry will be in "unused" state afterwards. + */ +static svn_error_t * +internal_close_file(svn_file_cache_t *cache, cache_entry_t *entry) +{ + /* any cached file handle held by the application must have either + * been returned or invalidated before, i.e. this entry must be "idle" */ + assert(! entry->open_handle); + + /* remove entry from the index (if it is in there) and the + * list of entries for the same file name + */ + if (entry->sibling_link.previous == NULL) + { + cache_entry_t *sibling = get_next_entry(&entry->sibling_link); + assert(!sibling || sibling->file); + + /* make sure the hash key does not depend on entry->pool + * by removing and possibly re-adding the hash entry + */ + apr_hash_set(cache->first_by_name, + entry->name, + APR_HASH_KEY_STRING, + NULL); + if (sibling) + apr_hash_set(cache->first_by_name, + sibling->name, + APR_HASH_KEY_STRING, + sibling); + } + + unlink_link(&entry->sibling_link); + + /* remove entry from the idle and global list */ + remove_from_list(&cache->idle_entries, &entry->idle_link); + remove_from_list(&cache->used_entries, &entry->global_link); + + /* actually close the file handle. */ + SVN_ERR(svn_io_file_close(entry->file, entry->pool)); + entry->file = NULL; + entry->name = NULL; + svn_pool_clear(entry->pool); + + /* entry may now be reused */ + append_to_list(&cache->unused_entries, &entry->global_link); + + return SVN_NO_ERROR; +} + +/* "Destructor" (APR pool item cleanup code) for cached file handles passed + * to the application. It ensures that the handle will be returned to the + * cache automatically upon pool cleanup. + */ +static apr_status_t +close_handle_before_cleanup(void *handle_void) +{ + svn_file_cache__handle_t *f = handle_void; + svn_error_t *err = SVN_NO_ERROR; + + /* if this hasn't been done before: + * "close" the handle, i.e. return it to the cache + */ + if (f->entry) + err = svn_file_cache__close(f); + + /* fully reset all members to prevent zombies doing damage */ + f->entry = NULL; + f->cache = NULL; + + return err ? err->apr_err : APR_SUCCESS; +} + +/* Create a cached file handle to be returned to the application in F for + * an idle ENTRY in CACHE. The cached file handle will be allocated in + * POOL and will automatically be returned to the cache when that pool + * is cleared or destroyed. + */ +static svn_error_t * +open_entry(svn_file_cache__handle_t **f, + svn_file_cache_t *cache, + cache_entry_t *entry, + apr_pool_t *pool) +{ + /* any entry can be handed out to the application only once at any time */ + assert (! entry->open_handle); + + /* the entry will no longer be idle */ + remove_from_list(&cache->idle_entries, &entry->idle_link); + + /* create and initialize the cached file handle structure */ + *f = apr_palloc(pool, sizeof(svn_file_cache__handle_t)); + (*f)->cache = cache; + (*f)->entry = entry; + entry->open_handle = *f; + + /* ensure proper cleanup, i.e. prevent handle leaks */ + apr_pool_cleanup_register(pool, + *f, + close_handle_before_cleanup, + apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} + +/* If there are idle entries, close the oldest one, i.e. closing the + * underlying APR file handle rendering the entry "unused". + */ +static svn_error_t * +close_oldest_idle(svn_file_cache_t *cache) +{ + return cache->idle_entries.first + ? internal_close_file(cache, cache->idle_entries.first->item) + : SVN_NO_ERROR; +} + +/* If we hold too many open files, close the oldest idle entry, + * if there is such an entry. + */ +static svn_error_t * +auto_close_oldest(svn_file_cache_t *cache) +{ + return cache->used_entries.count > cache->max_used_count + ? close_oldest_idle(cache) + : SVN_NO_ERROR; +} + +/* Test whether the file pointer in ENTRY is close enough to OFFSET to + * benefit from buffered access and is closer to it CLOSEST_ENTRY. + */ +static svn_boolean_t +pointer_is_closer(const cache_entry_t *entry, + apr_off_t offset, + const cache_entry_t *closest_entry) +{ + /* if the offset is unspecified, no entry will be considered a good + * match based on the file pointer's current position. + */ + if (offset == -1) + return FALSE; + + /* we also ignore entries who are no close enough to fit into the + * read buffer. */ + if ((entry->position - 0x1000 > offset) || + (entry->position + 0x1000 < offset)) + return FALSE; + + /* this is the closest match if we don't have a match, yet, at all */ + if (closest_entry == NULL) + return TRUE; + + /* is it a better match? */ + if ((offset < closest_entry->position ? offset - closest_entry->position + : closest_entry->position - offset) > + (offset < entry->position ? offset - entry->position + : entry->position - offset)) + return TRUE; + + return FALSE; +} + +/* Get an open file handle in F, for the file named FNAME with the open + * flag(s) in FLAG and permissions in PERM. These parameters are the same + * as in svn_io_file_open(). The file pointer will be moved to the specified + * OFFSET, if it is different from -1. + * + * If the COOKIE is not -1, only those file handles will be considerd a match + * that have been opened with that cookie before. This is particularly useful + * if OFFSET is -1, i.e. if the file pointer position of the handle returned + * is undefined. + */ +svn_error_t * +svn_file_cache__open(svn_file_cache__handle_t **f, + svn_file_cache_t *cache, + const char *fname, + apr_int32_t flag, + apr_fileperms_t perm, + apr_off_t offset, + int cookie, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + cache_entry_t *entry; + cache_entry_t *next_entry; + cache_entry_t *near_entry = NULL; + cache_entry_t *cookie_entry = NULL; + cache_entry_t *entry_found = NULL; + + /* begin cache access */ + SVN_ERR(lock_cache(cache)); + + /* look through all idle entries for this filename and find suitable ones */ + for (entry = find_first(cache, fname); entry; entry = next_entry) + { + next_entry = get_next_entry (&entry->sibling_link); + if (! entry->open_handle) + { + /* The entry is idle. Is it suitable? */ + if (entry->flag == flag && entry->perm == perm) + { + /* we can use this entry, if the cookie matches */ + if (entry->cookie == cookie && cookie != -1) + { + cookie_entry = entry; + + /* is it a particularly close match? */ + if (pointer_is_closer(entry, offset, near_entry)) + near_entry = entry; + } + } + else + { + /* the old file handle has been opened with different flags, + * e.g. this is "read" after "write" or vice versa. Therefore, + * close the underlying APR handle. + */ + internal_close_file(cache, entry); + } + } + } + + /* the best match we found */ + entry_found = near_entry ? near_entry : cookie_entry; + + if (entry_found) + { + /* we can use an idle entry */ + entry = entry_found; + + /* move the file pointer to the desired position */ + if (offset != -1) + err = svn_io_file_seek(entry->file, APR_SET, &offset, entry->pool); + } + else + { + /* we need a new entry. Make room for it */ + err = auto_close_oldest(cache); + + /* create a suitable idle entry */ + if (!err) + err = internal_file_open(&entry, cache, fname, flag, perm, cookie); + + /* move the file pointer to the desired position */ + if (!err && offset > 0) + err = svn_io_file_seek(entry->file, APR_SET, &offset, entry->pool); + } + + assert(err || entry->file); + + /* pass the cached file handle to the application (if there was no previous + * error) and finish the cache access. + */ + return unlock_cache(cache, err ? err : open_entry(f, cache, entry, pool)); +} + +/* Efficiently check whether the file handle cache CACHE holds an open + * handle to the file named FNAME. This is basically an efficient way to + * check that a file exists. However, a FALSE result does not mean that + * the respective file does not exist. + */ +svn_boolean_t +svn_file_cache__has_file(svn_file_cache_t *cache, const char *fname) +{ + svn_boolean_t result = FALSE; + + if (cache != NULL) + { + /* lock the cache and look for a used cache entry, i.e. an open + * APR level file handle. Only if the latter exists, we know for + * sure the file exists. Default to "FALSE" otherwise. + */ + svn_error_t *err = lock_cache(cache); + if (!err) + result = find_first(cache, fname) ? TRUE : FALSE; + + /* unlock the cache and return "file not cached" upon any error + * since this is the safe default. + */ + if (!err) + err = unlock_cache(cache, err); + + if (err) + { + svn_error_clear(err); + result = FALSE; + } + } + + return result; +} + +/* Return the APR level file handle underlying the cache file handle F. + * Returns NULL, if f is NULL, has already been closed or otherwise + * invalidated. + */ +apr_file_t * +svn_file_cache__get_apr_handle(svn_file_cache__handle_t *f) +{ + return (f && f->entry) ? f->entry->file : NULL; +} + +/* Return the name of the file that the cached handle f refers to. + * Returns NULL, if f is NULL, has already been closed or otherwise + * invalidated. + */ +const char * +svn_file_cache__get_name(svn_file_cache__handle_t *f) +{ + return (f && f->entry) ? f->entry->name : NULL; +} + +/* Return the cached file handle F to the cache. Depending on the number + * of open handles, the underlying handle may actually get closed. + */ +svn_error_t * +svn_file_cache__close(svn_file_cache__handle_t *f) +{ + svn_error_t *err = SVN_NO_ERROR; + svn_file_cache_t *cache = f ? f->cache : NULL; + cache_entry_t *entry = f ? f->entry : NULL; + + /* no-op for closed or invalidated cached file handles */ + if (cache == NULL || entry == NULL) + return SVN_NO_ERROR; + + /* mark the handle as "closed" / "invalid" */ + f->cache = NULL; + f->entry = NULL; + + /* begin actual cache access */ + SVN_ERR(lock_cache(cache)); + + /* mark cache entry as idle. + * It must actually manage the handle we are about to close. + */ + assert(entry->open_handle == f); + entry->open_handle = NULL; + + append_to_list(&cache->idle_entries, &entry->idle_link); + + /* remember the current file pointer so we can prefer this entry for + * accesses in the vicinity of this position. + */ + entry->position = 0; + err = svn_io_file_seek(entry->file, + APR_CUR, + &entry->position, + entry->pool); + + /* if all went well so far, reduce the number of cached file handles */ + if (!err) + err = auto_close_oldest(cache); + + /* end actual cache access */ + return unlock_cache(cache, err); +} + +/* Close all file handles currently not held by the application. + */ +svn_error_t * +svn_file_cache__flush(svn_file_cache_t *cache) +{ + svn_error_t *err = SVN_NO_ERROR; + + /* begin cache access */ + SVN_ERR(lock_cache(cache)); + + /* close all idle file handles */ + while (cache->idle_entries.count && !err) + err = close_oldest_idle(cache); + + /* if the application does not hold any cached file handles, we can + * discard all cache structures and re-allocate them to reduce the + * memory footprint. + */ + if (!err && !cache->used_entries.count) + { + /* release all cache data structures, in particular the ever-growing + * hash and the unused cache entries with all their sub-pools. + */ + svn_pool_clear(cache->pool); + + /* re-init global structures */ + cache->first_by_name = apr_hash_make(cache->pool); + init_list(&cache->unused_entries); + } + + /* end cache access */ + return unlock_cache(cache, err); +} + +/* Creates a new file handle cache in CACHE. Up to MAX_HANDLES file handles + * will be kept open. All cache-internal memory allocations during the caches' + * lifetime will be done from POOL. + * + * If the caller ensures that there are no concurrent accesses to the cache, + * THREAD_SAFE may be FALSE. Otherwise, it must be TRUE. + */ +svn_error_t * +svn_file_cache__create_cache(svn_file_cache_t **cache, + size_t max_handles, + svn_boolean_t thread_safe, + apr_pool_t *pool) +{ + /* allocate cache header */ + svn_file_cache_t *new_cache = + (svn_file_cache_t *)apr_palloc(pool, sizeof(*new_cache)); + + /* create sub-pool for all cache sub-structures */ + new_cache->pool = svn_pool_create(pool); + + /* initialize struct members */ + new_cache->max_used_count = max_handles; + + init_list(&new_cache->used_entries); + init_list(&new_cache->idle_entries); + init_list(&new_cache->unused_entries); + + new_cache->first_by_name = apr_hash_make(new_cache->pool); + new_cache->mutex = NULL; + + /* synchronization support may or may not be needed or available */ +#if APR_HAS_THREADS + if (thread_safe) + { + apr_status_t status = apr_thread_mutex_create(&(new_cache->mutex), + APR_THREAD_MUTEX_DEFAULT, + pool); + if (status) + return svn_error_wrap_apr(status, + _("Can't create cache mutex")); + } +#else + if (thread_safe) + return svn_error_wrap_apr(APR_ENOTIMPL, _("APR doesn't support threads")); +#endif + + /* done */ + *cache = new_cache; + return SVN_NO_ERROR; +} Propchange: subversion/branches/performance/subversion/libsvn_subr/svn_file_cache.c ------------------------------------------------------------------------------ svn:eol-style = native