Blame src/cache.c

Packit Service 20376f
/*
Packit Service 20376f
 * Copyright (C) the libgit2 contributors. All rights reserved.
Packit Service 20376f
 *
Packit Service 20376f
 * This file is part of libgit2, distributed under the GNU GPL v2 with
Packit Service 20376f
 * a Linking Exception. For full terms see the included COPYING file.
Packit Service 20376f
 */
Packit Service 20376f
Packit Service 20376f
#include "common.h"
Packit Service 20376f
#include "repository.h"
Packit Service 20376f
#include "commit.h"
Packit Service 20376f
#include "thread-utils.h"
Packit Service 20376f
#include "util.h"
Packit Service 20376f
#include "cache.h"
Packit Service 20376f
#include "odb.h"
Packit Service 20376f
#include "object.h"
Packit Service 20376f
#include "git2/oid.h"
Packit Service 20376f
Packit Service 20376f
bool git_cache__enabled = true;
Packit Service 20376f
ssize_t git_cache__max_storage = (256 * 1024 * 1024);
Packit Service 20376f
git_atomic_ssize git_cache__current_storage = {0};
Packit Service 20376f
Packit Service 20376f
static size_t git_cache__max_object_size[8] = {
Packit Service 20376f
	0,     /* GIT_OBJ__EXT1 */
Packit Service 20376f
	4096,  /* GIT_OBJ_COMMIT */
Packit Service 20376f
	4096,  /* GIT_OBJ_TREE */
Packit Service 20376f
	0,     /* GIT_OBJ_BLOB */
Packit Service 20376f
	4096,  /* GIT_OBJ_TAG */
Packit Service 20376f
	0,     /* GIT_OBJ__EXT2 */
Packit Service 20376f
	0,     /* GIT_OBJ_OFS_DELTA */
Packit Service 20376f
	0      /* GIT_OBJ_REF_DELTA */
Packit Service 20376f
};
Packit Service 20376f
Packit Service 20376f
int git_cache_set_max_object_size(git_otype type, size_t size)
Packit Service 20376f
{
Packit Service 20376f
	if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
Packit Service 20376f
		giterr_set(GITERR_INVALID, "type out of range");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	git_cache__max_object_size[type] = size;
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_cache_dump_stats(git_cache *cache)
Packit Service 20376f
{
Packit Service 20376f
	git_cached_obj *object;
Packit Service 20376f
Packit Service 20376f
	if (git_cache_size(cache) == 0)
Packit Service 20376f
		return;
Packit Service 20376f
Packit Service 20376f
	printf("Cache %p: %"PRIuZ" items cached, %"PRIdZ" bytes\n",
Packit Service 20376f
		cache, git_cache_size(cache), cache->used_memory);
Packit Service 20376f
Packit Service 20376f
	git_oidmap_foreach_value(cache->map, object, {
Packit Service 20376f
		char oid_str[9];
Packit Service 20376f
		printf(" %s%c %s (%"PRIuZ")\n",
Packit Service 20376f
			git_object_type2string(object->type),
Packit Service 20376f
			object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
Packit Service 20376f
			git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
Packit Service 20376f
			object->size
Packit Service 20376f
		);
Packit Service 20376f
	});
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_cache_init(git_cache *cache)
Packit Service 20376f
{
Packit Service 20376f
	memset(cache, 0, sizeof(*cache));
Packit Service 20376f
	cache->map = git_oidmap_alloc();
Packit Service 20376f
	GITERR_CHECK_ALLOC(cache->map);
Packit Service 20376f
	if (git_rwlock_init(&cache->lock)) {
Packit Service 20376f
		giterr_set(GITERR_OS, "failed to initialize cache rwlock");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
/* called with lock */
Packit Service 20376f
static void clear_cache(git_cache *cache)
Packit Service 20376f
{
Packit Service 20376f
	git_cached_obj *evict = NULL;
Packit Service 20376f
Packit Service 20376f
	if (git_cache_size(cache) == 0)
Packit Service 20376f
		return;
Packit Service 20376f
Packit Service 20376f
	git_oidmap_foreach_value(cache->map, evict, {
Packit Service 20376f
		git_cached_obj_decref(evict);
Packit Service 20376f
	});
Packit Service 20376f
Packit Service 20376f
	git_oidmap_clear(cache->map);
Packit Service 20376f
	git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
Packit Service 20376f
	cache->used_memory = 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_cache_clear(git_cache *cache)
Packit Service 20376f
{
Packit Service 20376f
	if (git_rwlock_wrlock(&cache->lock) < 0)
Packit Service 20376f
		return;
Packit Service 20376f
Packit Service 20376f
	clear_cache(cache);
Packit Service 20376f
Packit Service 20376f
	git_rwlock_wrunlock(&cache->lock);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_cache_free(git_cache *cache)
Packit Service 20376f
{
Packit Service 20376f
	git_cache_clear(cache);
Packit Service 20376f
	git_oidmap_free(cache->map);
Packit Service 20376f
	git_rwlock_free(&cache->lock);
Packit Service 20376f
	git__memzero(cache, sizeof(*cache));
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
/* Called with lock */
Packit Service 20376f
static void cache_evict_entries(git_cache *cache)
Packit Service 20376f
{
Packit Service 20376f
	uint32_t seed = rand();
Packit Service 20376f
	size_t evict_count = 8;
Packit Service 20376f
	ssize_t evicted_memory = 0;
Packit Service 20376f
Packit Service 20376f
	/* do not infinite loop if there's not enough entries to evict  */
Packit Service 20376f
	if (evict_count > git_cache_size(cache)) {
Packit Service 20376f
		clear_cache(cache);
Packit Service 20376f
		return;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	while (evict_count > 0) {
Packit Service 20376f
		khiter_t pos = seed++ % git_oidmap_end(cache->map);
Packit Service 20376f
Packit Service 20376f
		if (git_oidmap_has_data(cache->map, pos)) {
Packit Service 20376f
			git_cached_obj *evict = git_oidmap_value_at(cache->map, pos);
Packit Service 20376f
Packit Service 20376f
			evict_count--;
Packit Service 20376f
			evicted_memory += evict->size;
Packit Service 20376f
			git_cached_obj_decref(evict);
Packit Service 20376f
Packit Service 20376f
			git_oidmap_delete_at(cache->map, pos);
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	cache->used_memory -= evicted_memory;
Packit Service 20376f
	git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static bool cache_should_store(git_otype object_type, size_t object_size)
Packit Service 20376f
{
Packit Service 20376f
	size_t max_size = git_cache__max_object_size[object_type];
Packit Service 20376f
	return git_cache__enabled && object_size < max_size;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
Packit Service 20376f
{
Packit Service 20376f
	khiter_t pos;
Packit Service 20376f
	git_cached_obj *entry = NULL;
Packit Service 20376f
Packit Service 20376f
	if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
Packit Service 20376f
		return NULL;
Packit Service 20376f
Packit Service 20376f
	pos = git_oidmap_lookup_index(cache->map, oid);
Packit Service 20376f
	if (git_oidmap_valid_index(cache->map, pos)) {
Packit Service 20376f
		entry = git_oidmap_value_at(cache->map, pos);
Packit Service 20376f
Packit Service 20376f
		if (flags && entry->flags != flags) {
Packit Service 20376f
			entry = NULL;
Packit Service 20376f
		} else {
Packit Service 20376f
			git_cached_obj_incref(entry);
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	git_rwlock_rdunlock(&cache->lock);
Packit Service 20376f
Packit Service 20376f
	return entry;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static void *cache_store(git_cache *cache, git_cached_obj *entry)
Packit Service 20376f
{
Packit Service 20376f
	khiter_t pos;
Packit Service 20376f
Packit Service 20376f
	git_cached_obj_incref(entry);
Packit Service 20376f
Packit Service 20376f
	if (!git_cache__enabled && cache->used_memory > 0) {
Packit Service 20376f
		git_cache_clear(cache);
Packit Service 20376f
		return entry;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (!cache_should_store(entry->type, entry->size))
Packit Service 20376f
		return entry;
Packit Service 20376f
Packit Service 20376f
	if (git_rwlock_wrlock(&cache->lock) < 0)
Packit Service 20376f
		return entry;
Packit Service 20376f
Packit Service 20376f
	/* soften the load on the cache */
Packit Service 20376f
	if (git_cache__current_storage.val > git_cache__max_storage)
Packit Service 20376f
		cache_evict_entries(cache);
Packit Service 20376f
Packit Service 20376f
	pos = git_oidmap_lookup_index(cache->map, &entry->oid);
Packit Service 20376f
Packit Service 20376f
	/* not found */
Packit Service 20376f
	if (!git_oidmap_valid_index(cache->map, pos)) {
Packit Service 20376f
		int rval;
Packit Service 20376f
Packit Service 20376f
		git_oidmap_insert(cache->map, &entry->oid, entry, &rval);
Packit Service 20376f
		if (rval >= 0) {
Packit Service 20376f
			git_cached_obj_incref(entry);
Packit Service 20376f
			cache->used_memory += entry->size;
Packit Service 20376f
			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
	/* found */
Packit Service 20376f
	else {
Packit Service 20376f
		git_cached_obj *stored_entry = git_oidmap_value_at(cache->map, pos);
Packit Service 20376f
Packit Service 20376f
		if (stored_entry->flags == entry->flags) {
Packit Service 20376f
			git_cached_obj_decref(entry);
Packit Service 20376f
			git_cached_obj_incref(stored_entry);
Packit Service 20376f
			entry = stored_entry;
Packit Service 20376f
		} else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
Packit Service 20376f
			entry->flags == GIT_CACHE_STORE_PARSED) {
Packit Service 20376f
			git_cached_obj_decref(stored_entry);
Packit Service 20376f
			git_cached_obj_incref(entry);
Packit Service 20376f
Packit Service 20376f
			git_oidmap_set_key_at(cache->map, pos, &entry->oid);
Packit Service 20376f
			git_oidmap_set_value_at(cache->map, pos, entry);
Packit Service 20376f
		} else {
Packit Service 20376f
			/* NO OP */
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	git_rwlock_wrunlock(&cache->lock);
Packit Service 20376f
	return entry;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
Packit Service 20376f
{
Packit Service 20376f
	entry->cached.flags = GIT_CACHE_STORE_RAW;
Packit Service 20376f
	return cache_store(cache, (git_cached_obj *)entry);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void *git_cache_store_parsed(git_cache *cache, git_object *entry)
Packit Service 20376f
{
Packit Service 20376f
	entry->cached.flags = GIT_CACHE_STORE_PARSED;
Packit Service 20376f
	return cache_store(cache, (git_cached_obj *)entry);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
Packit Service 20376f
{
Packit Service 20376f
	return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
Packit Service 20376f
{
Packit Service 20376f
	return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void *git_cache_get_any(git_cache *cache, const git_oid *oid)
Packit Service 20376f
{
Packit Service 20376f
	return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_cached_obj_decref(void *_obj)
Packit Service 20376f
{
Packit Service 20376f
	git_cached_obj *obj = _obj;
Packit Service 20376f
Packit Service 20376f
	if (git_atomic_dec(&obj->refcount) == 0) {
Packit Service 20376f
		switch (obj->flags) {
Packit Service 20376f
		case GIT_CACHE_STORE_RAW:
Packit Service 20376f
			git_odb_object__free(_obj);
Packit Service 20376f
			break;
Packit Service 20376f
Packit Service 20376f
		case GIT_CACHE_STORE_PARSED:
Packit Service 20376f
			git_object__free(_obj);
Packit Service 20376f
			break;
Packit Service 20376f
Packit Service 20376f
		default:
Packit Service 20376f
			git__free(_obj);
Packit Service 20376f
			break;
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
}