summaryrefslogtreecommitdiff
path: root/src/cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cache.c')
-rw-r--r--src/cache.c261
1 files changed, 206 insertions, 55 deletions
diff --git a/src/cache.c b/src/cache.c
index e7f333577..ca122fb77 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -11,100 +11,251 @@
#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)
+GIT__USE_OIDMAP
+
+bool git_cache__enabled = true;
+size_t git_cache__max_storage = (4 * 1024 * 1024);
+
+static size_t git_cache__max_object_size[8] = {
+ 0, /* GIT_OBJ__EXT1 */
+ 4096, /* GIT_OBJ_COMMIT */
+ 4096, /* GIT_OBJ_TREE */
+ 0, /* GIT_OBJ_BLOB */
+ 4096, /* GIT_OBJ_TAG */
+ 0, /* GIT_OBJ__EXT2 */
+ 0, /* GIT_OBJ_OFS_DELTA */
+ 0 /* GIT_OBJ_REF_DELTA */
+};
+
+int git_cache_set_max_object_size(git_otype 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)) {
+ giterr_set(GITERR_INVALID, "type out of range");
+ return -1;
+ }
+
+ git_cache__max_object_size[type] = size;
+ return 0;
+}
- cache->size_mask = size - 1;
- cache->lru_count = 0;
- cache->free_obj = free_ptr;
+void git_cache_dump_stats(git_cache *cache)
+{
+ git_cached_obj *object;
- git_mutex_init(&cache->lock);
+ if (kh_size(cache->map) == 0)
+ return;
- cache->nodes = git__malloc(size * sizeof(git_cached_obj *));
- GITERR_CHECK_ALLOC(cache->nodes);
+ printf("Cache %p: %d items cached, %d bytes\n",
+ cache, kh_size(cache->map), (int)cache->used_memory);
- memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *));
+ kh_foreach_value(cache->map, object, {
+ char oid_str[9];
+ printf(" %s%c %s (%d)\n",
+ git_object_type2string(object->type),
+ object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
+ git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
+ (int)object->size
+ );
+ });
+}
+
+int git_cache_init(git_cache *cache)
+{
+ cache->used_memory = 0;
+ cache->map = git_oidmap_alloc();
+ git_mutex_init(&cache->lock);
return 0;
}
+/* called with lock */
+static void clear_cache(git_cache *cache)
+{
+ git_cached_obj *evict = NULL;
+
+ kh_foreach_value(cache->map, evict, {
+ git_cached_obj_decref(evict);
+ });
+
+ kh_clear(oid, cache->map);
+ cache->used_memory = 0;
+}
+
+void git_cache_clear(git_cache *cache)
+{
+ if (git_mutex_lock(&cache->lock) < 0)
+ return;
+
+ clear_cache(cache);
+
+ git_mutex_unlock(&cache->lock);
+}
+
void git_cache_free(git_cache *cache)
{
- size_t i;
+ git_cache_clear(cache);
+
+ git_oidmap_free(cache->map);
+ git_mutex_free(&cache->lock);
+}
+
+/* Called with lock */
+static void cache_evict_entries(git_cache *cache)
+{
+ uint32_t seed = rand();
+ size_t evict_count = 8;
- for (i = 0; i < (cache->size_mask + 1); ++i) {
- if (cache->nodes[i] != NULL)
- git_cached_obj_decref(cache->nodes[i], cache->free_obj);
+ /* do not infinite loop if there's not enough entries to evict */
+ if (evict_count > kh_size(cache->map)) {
+ clear_cache(cache);
+ return;
}
- git_mutex_free(&cache->lock);
- git__free(cache->nodes);
+ while (evict_count > 0) {
+ khiter_t pos = seed++ % kh_end(cache->map);
+
+ if (kh_exist(cache->map, pos)) {
+ git_cached_obj *evict = kh_val(cache->map, pos);
+
+ evict_count--;
+ cache->used_memory -= evict->size;
+ git_cached_obj_decref(evict);
+
+ kh_del(oid, cache->map, pos);
+ }
+ }
}
-void *git_cache_get(git_cache *cache, const git_oid *oid)
+static bool cache_should_store(git_otype object_type, size_t object_size)
{
- uint32_t hash;
- git_cached_obj *node = NULL, *result = NULL;
+ size_t max_size = git_cache__max_object_size[object_type];
+ return git_cache__enabled && object_size < max_size;
+}
- memcpy(&hash, oid->id, sizeof(hash));
+static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
+{
+ khiter_t pos;
+ git_cached_obj *entry = NULL;
- if (git_mutex_lock(&cache->lock)) {
- giterr_set(GITERR_THREAD, "unable to lock cache mutex");
+ if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0)
return NULL;
- }
- {
- node = cache->nodes[hash & cache->size_mask];
+ pos = kh_get(oid, cache->map, oid);
+ if (pos != kh_end(cache->map)) {
+ entry = kh_val(cache->map, pos);
- if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) {
- git_cached_obj_incref(node);
- result = node;
+ if (flags && entry->flags != flags) {
+ entry = NULL;
+ } else {
+ git_cached_obj_incref(entry);
}
}
+
git_mutex_unlock(&cache->lock);
- return result;
+ return entry;
}
-void *git_cache_try_store(git_cache *cache, void *_entry)
+static void *cache_store(git_cache *cache, git_cached_obj *entry)
{
- git_cached_obj *entry = _entry;
- uint32_t hash;
+ khiter_t pos;
- memcpy(&hash, &entry->oid, sizeof(uint32_t));
+ git_cached_obj_incref(entry);
- if (git_mutex_lock(&cache->lock)) {
- giterr_set(GITERR_THREAD, "unable to lock cache mutex");
- return NULL;
- }
+ if (!cache_should_store(entry->type, entry->size))
+ return entry;
- {
- git_cached_obj *node = cache->nodes[hash & cache->size_mask];
+ if (git_mutex_lock(&cache->lock) < 0)
+ return entry;
- /* increase the refcount on this object, because
- * the cache now owns it */
- git_cached_obj_incref(entry);
+ if (cache->used_memory > git_cache__max_storage)
+ cache_evict_entries(cache);
- 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;
+ pos = kh_get(oid, cache->map, &entry->oid);
+
+ /* not found */
+ if (pos == kh_end(cache->map)) {
+ int rval;
+
+ pos = kh_put(oid, cache->map, &entry->oid, &rval);
+ if (rval >= 0) {
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ git_cached_obj_incref(entry);
+ cache->used_memory += entry->size;
}
+ }
+ /* found */
+ else {
+ git_cached_obj *stored_entry = kh_val(cache->map, pos);
- /* increase the refcount again, because we are
- * returning it to the user */
- git_cached_obj_incref(entry);
+ 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) {
+ git_cached_obj_decref(stored_entry);
+ git_cached_obj_incref(entry);
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ } else {
+ /* NO OP */
+ }
}
- git_mutex_unlock(&cache->lock);
+ git_mutex_unlock(&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;
+ }
+ }
+}