mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-05 17:12:51 +00:00
281 lines
6.2 KiB
C
281 lines
6.2 KiB
C
/*
|
|
* 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 "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"
|
|
|
|
GIT__USE_OIDMAP
|
|
|
|
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_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 (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;
|
|
}
|
|
|
|
void git_cache_dump_stats(git_cache *cache)
|
|
{
|
|
git_cached_obj *object;
|
|
|
|
if (kh_size(cache->map) == 0)
|
|
return;
|
|
|
|
printf("Cache %p: %d items cached, %d bytes\n",
|
|
cache, kh_size(cache->map), (int)cache->used_memory);
|
|
|
|
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)
|
|
{
|
|
memset(cache, 0, sizeof(*cache));
|
|
cache->map = git_oidmap_alloc();
|
|
if (git_mutex_init(&cache->lock)) {
|
|
giterr_set(GITERR_OS, "Failed to initialize cache mutex");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* called with lock */
|
|
static void clear_cache(git_cache *cache)
|
|
{
|
|
git_cached_obj *evict = NULL;
|
|
|
|
if (kh_size(cache->map) == 0)
|
|
return;
|
|
|
|
kh_foreach_value(cache->map, evict, {
|
|
git_cached_obj_decref(evict);
|
|
});
|
|
|
|
kh_clear(oid, 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_mutex_lock(&cache->lock) < 0)
|
|
return;
|
|
|
|
clear_cache(cache);
|
|
|
|
git_mutex_unlock(&cache->lock);
|
|
}
|
|
|
|
void git_cache_free(git_cache *cache)
|
|
{
|
|
git_cache_clear(cache);
|
|
git_oidmap_free(cache->map);
|
|
git_mutex_free(&cache->lock);
|
|
git__memzero(cache, sizeof(*cache));
|
|
}
|
|
|
|
/* Called with lock */
|
|
static void cache_evict_entries(git_cache *cache)
|
|
{
|
|
uint32_t seed = rand();
|
|
size_t evict_count = 8;
|
|
ssize_t evicted_memory = 0;
|
|
|
|
/* do not infinite loop if there's not enough entries to evict */
|
|
if (evict_count > kh_size(cache->map)) {
|
|
clear_cache(cache);
|
|
return;
|
|
}
|
|
|
|
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--;
|
|
evicted_memory += evict->size;
|
|
git_cached_obj_decref(evict);
|
|
|
|
kh_del(oid, cache->map, pos);
|
|
}
|
|
}
|
|
|
|
cache->used_memory -= evicted_memory;
|
|
git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
|
|
}
|
|
|
|
static bool cache_should_store(git_otype 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)
|
|
{
|
|
khiter_t pos;
|
|
git_cached_obj *entry = NULL;
|
|
|
|
if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0)
|
|
return NULL;
|
|
|
|
pos = kh_get(oid, cache->map, oid);
|
|
if (pos != kh_end(cache->map)) {
|
|
entry = kh_val(cache->map, pos);
|
|
|
|
if (flags && entry->flags != flags) {
|
|
entry = NULL;
|
|
} else {
|
|
git_cached_obj_incref(entry);
|
|
}
|
|
}
|
|
|
|
git_mutex_unlock(&cache->lock);
|
|
|
|
return entry;
|
|
}
|
|
|
|
static void *cache_store(git_cache *cache, git_cached_obj *entry)
|
|
{
|
|
khiter_t pos;
|
|
|
|
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_mutex_lock(&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);
|
|
|
|
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;
|
|
git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
|
|
}
|
|
}
|
|
/* found */
|
|
else {
|
|
git_cached_obj *stored_entry = kh_val(cache->map, pos);
|
|
|
|
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);
|
|
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;
|
|
}
|
|
}
|
|
}
|