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


Reply via email to