/*
- * 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;
+ }
+ }
+}