blob: af42b3959552f726907dd49a3c23fde42efd86f1 [file] [log] [blame]
/*
* 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 "cache.h"
#include "repository.h"
#include "commit.h"
#include "thread-utils.h"
#include "util.h"
#include "odb.h"
#include "object.h"
#include "git2/oid.h"
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 (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
git_error_set(GIT_ERROR_INVALID, "type out of range");
return -1;
}
git_cache__max_object_size[type] = size;
return 0;
}
void git_cache_dump_stats(git_cache *cache)
{
git_cached_obj *object;
if (git_cache_size(cache) == 0)
return;
printf("Cache %p: %"PRIuZ" items cached, %"PRIdZ" bytes\n",
cache, git_cache_size(cache), cache->used_memory);
git_oidmap_foreach_value(cache->map, object, {
char oid_str[9];
printf(" %s%c %s (%"PRIuZ")\n",
git_object_type2string(object->type),
object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
object->size
);
});
}
int git_cache_init(git_cache *cache)
{
memset(cache, 0, sizeof(*cache));
if ((git_oidmap_new(&cache->map)) < 0)
return -1;
if (git_rwlock_init(&cache->lock)) {
git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
return -1;
}
return 0;
}
/* called with lock */
static void clear_cache(git_cache *cache)
{
git_cached_obj *evict = NULL;
if (git_cache_size(cache) == 0)
return;
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_clear(git_cache *cache)
{
if (git_rwlock_wrlock(&cache->lock) < 0)
return;
clear_cache(cache);
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;
}
i = 0;
while (evict_count > 0) {
git_cached_obj *evict;
const git_oid *key;
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);
}
cache->used_memory -= evicted_memory;
git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
}
static bool cache_should_store(git_object_t object_type, size_t object_size)
{
size_t max_size = git_cache__max_object_size[object_type];
return git_cache__enabled && object_size < max_size;
}
static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
{
git_cached_obj *entry;
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_rwlock_rdunlock(&cache->lock);
return entry;
}
static void *cache_store(git_cache *cache, git_cached_obj *entry)
{
git_cached_obj *stored_entry;
git_cached_obj_incref(entry);
if (!git_cache__enabled && cache->used_memory > 0) {
git_cache_clear(cache);
return entry;
}
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_cache__current_storage.val > 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;
}
}
}