]> git.proxmox.com Git - libgit2.git/blobdiff - src/cache.c
New upstream version 1.1.0+dfsg.1
[libgit2.git] / src / cache.c
index cbd360a02ab7bdec3180efc8f6b9a7a784caef87..a76da50d7a1d18548481939d7f9519125ace1d02 100644 (file)
 /*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * Copyright (C) the libgit2 contributors. All rights reserved.
  *
  * This file is part of libgit2, distributed under the GNU GPL v2 with
  * a Linking Exception. For full terms see the included COPYING file.
  */
 
-#include "common.h"
+#include "cache.h"
+
 #include "repository.h"
 #include "commit.h"
 #include "thread-utils.h"
 #include "util.h"
-#include "cache.h"
+#include "odb.h"
+#include "object.h"
 #include "git2/oid.h"
 
-int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr)
+bool git_cache__enabled = true;
+ssize_t git_cache__max_storage = (256 * 1024 * 1024);
+git_atomic_ssize git_cache__current_storage = {0};
+
+static size_t git_cache__max_object_size[8] = {
+       0,     /* GIT_OBJECT__EXT1 */
+       4096,  /* GIT_OBJECT_COMMIT */
+       4096,  /* GIT_OBJECT_TREE */
+       0,     /* GIT_OBJECT_BLOB */
+       4096,  /* GIT_OBJECT_TAG */
+       0,     /* GIT_OBJECT__EXT2 */
+       0,     /* GIT_OBJECT_OFS_DELTA */
+       0      /* GIT_OBJECT_REF_DELTA */
+};
+
+int git_cache_set_max_object_size(git_object_t type, size_t size)
 {
-       if (size < 8)
-               size = 8;
-       size = git__size_t_powerof2(size);
+       if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
+               git_error_set(GIT_ERROR_INVALID, "type out of range");
+               return -1;
+       }
 
-       cache->size_mask = size - 1;
-       cache->lru_count = 0;
-       cache->free_obj = free_ptr;
+       git_cache__max_object_size[type] = size;
+       return 0;
+}
+
+int git_cache_init(git_cache *cache)
+{
+       memset(cache, 0, sizeof(*cache));
 
-       git_mutex_init(&cache->lock);
+       if ((git_oidmap_new(&cache->map)) < 0)
+               return -1;
 
-       cache->nodes = git__malloc(size * sizeof(git_cached_obj *));
-       GITERR_CHECK_ALLOC(cache->nodes);
+       if (git_rwlock_init(&cache->lock)) {
+               git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
+               return -1;
+       }
 
-       memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *));
        return 0;
 }
 
-void git_cache_free(git_cache *cache)
+/* called with lock */
+static void clear_cache(git_cache *cache)
 {
-       size_t i;
+       git_cached_obj *evict = NULL;
 
-       for (i = 0; i < (cache->size_mask + 1); ++i) {
-               if (cache->nodes[i] != NULL)
-                       git_cached_obj_decref(cache->nodes[i], cache->free_obj);
-       }
+       if (git_cache_size(cache) == 0)
+               return;
 
-       git_mutex_free(&cache->lock);
-       git__free(cache->nodes);
+       git_oidmap_foreach_value(cache->map, evict, {
+               git_cached_obj_decref(evict);
+       });
+
+       git_oidmap_clear(cache->map);
+       git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
+       cache->used_memory = 0;
 }
 
-void *git_cache_get(git_cache *cache, const git_oid *oid)
+void git_cache_clear(git_cache *cache)
 {
-       uint32_t hash;
-       git_cached_obj *node = NULL, *result = NULL;
+       if (git_rwlock_wrlock(&cache->lock) < 0)
+               return;
 
-       memcpy(&hash, oid->id, sizeof(hash));
+       clear_cache(cache);
 
-       if (git_mutex_lock(&cache->lock)) {
-               giterr_set(GITERR_THREAD, "unable to lock cache mutex");
-               return NULL;
+       git_rwlock_wrunlock(&cache->lock);
+}
+
+void git_cache_dispose(git_cache *cache)
+{
+       git_cache_clear(cache);
+       git_oidmap_free(cache->map);
+       git_rwlock_free(&cache->lock);
+       git__memzero(cache, sizeof(*cache));
+}
+
+/* Called with lock */
+static void cache_evict_entries(git_cache *cache)
+{
+       size_t evict_count = git_cache_size(cache) / 2048, i;
+       ssize_t evicted_memory = 0;
+
+       if (evict_count < 8)
+               evict_count = 8;
+
+       /* do not infinite loop if there's not enough entries to evict  */
+       if (evict_count > git_cache_size(cache)) {
+               clear_cache(cache);
+               return;
        }
 
-       {
-               node = cache->nodes[hash & cache->size_mask];
+       i = 0;
+       while (evict_count > 0) {
+               git_cached_obj *evict;
+               const git_oid *key;
 
-               if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) {
-                       git_cached_obj_incref(node);
-                       result = node;
-               }
+               if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER)
+                       break;
+
+               evict_count--;
+               evicted_memory += evict->size;
+               git_oidmap_delete(cache->map, key);
+               git_cached_obj_decref(evict);
        }
-       git_mutex_unlock(&cache->lock);
 
-       return result;
+       cache->used_memory -= evicted_memory;
+       git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
 }
 
-void *git_cache_try_store(git_cache *cache, void *_entry)
+static bool cache_should_store(git_object_t object_type, size_t object_size)
 {
-       git_cached_obj *entry = _entry;
-       uint32_t hash;
+       size_t max_size = git_cache__max_object_size[object_type];
+       return git_cache__enabled && object_size < max_size;
+}
 
-       memcpy(&hash, &entry->oid, sizeof(uint32_t));
+static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
+{
+       git_cached_obj *entry;
 
-       if (git_mutex_lock(&cache->lock)) {
-               giterr_set(GITERR_THREAD, "unable to lock cache mutex");
+       if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
                return NULL;
+
+       if ((entry = git_oidmap_get(cache->map, oid)) != NULL) {
+               if (flags && entry->flags != flags) {
+                       entry = NULL;
+               } else {
+                       git_cached_obj_incref(entry);
+               }
        }
 
-       {
-               git_cached_obj *node = cache->nodes[hash & cache->size_mask];
+       git_rwlock_rdunlock(&cache->lock);
 
-               /* increase the refcount on this object, because
-                * the cache now owns it */
-               git_cached_obj_incref(entry);
+       return entry;
+}
 
-               if (node == NULL) {
-                       cache->nodes[hash & cache->size_mask] = entry;
-               } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) {
-                       git_cached_obj_decref(entry, cache->free_obj);
-                       entry = node;
-               } else {
-                       git_cached_obj_decref(node, cache->free_obj);
-                       cache->nodes[hash & cache->size_mask] = entry;
-               }
+static void *cache_store(git_cache *cache, git_cached_obj *entry)
+{
+       git_cached_obj *stored_entry;
 
-               /* increase the refcount again, because we are
-                * returning it to the user */
-               git_cached_obj_incref(entry);
+       git_cached_obj_incref(entry);
 
+       if (!git_cache__enabled && cache->used_memory > 0) {
+               git_cache_clear(cache);
+               return entry;
        }
-       git_mutex_unlock(&cache->lock);
 
+       if (!cache_should_store(entry->type, entry->size))
+               return entry;
+
+       if (git_rwlock_wrlock(&cache->lock) < 0)
+               return entry;
+
+       /* soften the load on the cache */
+       if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage)
+               cache_evict_entries(cache);
+
+       /* not found */
+       if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) {
+               if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
+                       git_cached_obj_incref(entry);
+                       cache->used_memory += entry->size;
+                       git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
+               }
+       }
+       /* found */
+       else {
+               if (stored_entry->flags == entry->flags) {
+                       git_cached_obj_decref(entry);
+                       git_cached_obj_incref(stored_entry);
+                       entry = stored_entry;
+               } else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
+                          entry->flags == GIT_CACHE_STORE_PARSED) {
+                       if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
+                               git_cached_obj_decref(stored_entry);
+                               git_cached_obj_incref(entry);
+                       } else {
+                               git_cached_obj_decref(entry);
+                               git_cached_obj_incref(stored_entry);
+                               entry = stored_entry;
+                       }
+               } else {
+                       /* NO OP */
+               }
+       }
+
+       git_rwlock_wrunlock(&cache->lock);
        return entry;
 }
+
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
+{
+       entry->cached.flags = GIT_CACHE_STORE_RAW;
+       return cache_store(cache, (git_cached_obj *)entry);
+}
+
+void *git_cache_store_parsed(git_cache *cache, git_object *entry)
+{
+       entry->cached.flags = GIT_CACHE_STORE_PARSED;
+       return cache_store(cache, (git_cached_obj *)entry);
+}
+
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
+{
+       return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
+}
+
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
+{
+       return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
+}
+
+void *git_cache_get_any(git_cache *cache, const git_oid *oid)
+{
+       return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
+}
+
+void git_cached_obj_decref(void *_obj)
+{
+       git_cached_obj *obj = _obj;
+
+       if (git_atomic_dec(&obj->refcount) == 0) {
+               switch (obj->flags) {
+               case GIT_CACHE_STORE_RAW:
+                       git_odb_object__free(_obj);
+                       break;
+
+               case GIT_CACHE_STORE_PARSED:
+                       git_object__free(_obj);
+                       break;
+
+               default:
+                       git__free(_obj);
+                       break;
+               }
+       }
+}