cache.c 6.21 KB
Newer Older
Vicent Marti committed
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
Vicent Marti committed
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
Vicent Marti committed
6 7 8 9 10 11
 */

#include "common.h"
#include "repository.h"
#include "commit.h"
#include "thread-utils.h"
12
#include "util.h"
Vicent Marti committed
13
#include "cache.h"
14 15
#include "odb.h"
#include "object.h"
16
#include "git2/oid.h"
Vicent Marti committed
17

18
GIT__USE_OIDMAP
Vicent Marti committed
19

Vicent Marti committed
20
bool git_cache__enabled = true;
21 22
ssize_t git_cache__max_storage = (256 * 1024 * 1024);
git_atomic_ssize git_cache__current_storage = {0};
Vicent Marti committed
23

24 25
static size_t git_cache__max_object_size[8] = {
	0,     /* GIT_OBJ__EXT1 */
Vicent Marti committed
26 27
	4096,  /* GIT_OBJ_COMMIT */
	4096,  /* GIT_OBJ_TREE */
28
	0,     /* GIT_OBJ_BLOB */
Vicent Marti committed
29
	4096,  /* GIT_OBJ_TAG */
30 31 32
	0,     /* GIT_OBJ__EXT2 */
	0,     /* GIT_OBJ_OFS_DELTA */
	0      /* GIT_OBJ_REF_DELTA */
Vicent Marti committed
33 34
};

35 36 37 38 39 40 41 42 43 44 45
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;
}

Vicent Marti committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
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
		);
	});
}

67 68
int git_cache_init(git_cache *cache)
{
69
	memset(cache, 0, sizeof(*cache));
70
	cache->map = git_oidmap_alloc();
Russell Belfer committed
71 72 73 74
	if (git_mutex_init(&cache->lock)) {
		giterr_set(GITERR_OS, "Failed to initialize cache mutex");
		return -1;
	}
75
	return 0;
Vicent Marti committed
76 77
}

78 79
/* called with lock */
static void clear_cache(git_cache *cache)
80 81 82
{
	git_cached_obj *evict = NULL;

83 84 85
	if (kh_size(cache->map) == 0)
		return;

86 87 88 89 90
	kh_foreach_value(cache->map, evict, {
		git_cached_obj_decref(evict);
	});

	kh_clear(oid, cache->map);
91
	git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
92
	cache->used_memory = 0;
93 94 95 96 97 98 99 100
}

void git_cache_clear(git_cache *cache)
{
	if (git_mutex_lock(&cache->lock) < 0)
		return;

	clear_cache(cache);
101 102 103 104

	git_mutex_unlock(&cache->lock);
}

105 106 107 108 109
void git_cache_free(git_cache *cache)
{
	git_cache_clear(cache);
	git_oidmap_free(cache->map);
	git_mutex_free(&cache->lock);
110
	git__memzero(cache, sizeof(*cache));
111 112
}

113 114
/* Called with lock */
static void cache_evict_entries(git_cache *cache)
Vicent Marti committed
115 116
{
	uint32_t seed = rand();
117 118
	size_t evict_count = 8;
	ssize_t evicted_memory = 0;
Vicent Marti committed
119 120

	/* do not infinite loop if there's not enough entries to evict  */
121
	if (evict_count > kh_size(cache->map)) {
122
		clear_cache(cache);
Vicent Marti committed
123
		return;
124
	}
Vicent Marti committed
125

Vicent Marti committed
126
	while (evict_count > 0) {
Vicent Marti committed
127 128 129
		khiter_t pos = seed++ % kh_end(cache->map);

		if (kh_exist(cache->map, pos)) {
Vicent Marti committed
130 131 132
			git_cached_obj *evict = kh_val(cache->map, pos);

			evict_count--;
133
			evicted_memory += evict->size;
Vicent Marti committed
134 135
			git_cached_obj_decref(evict);

Vicent Marti committed
136 137 138
			kh_del(oid, cache->map, pos);
		}
	}
139 140

	cache->used_memory -= evicted_memory;
141
	git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
Vicent Marti committed
142 143
}

Vicent Marti committed
144
static bool cache_should_store(git_otype object_type, size_t object_size)
Vicent Marti committed
145
{
Vicent Marti committed
146
	size_t max_size = git_cache__max_object_size[object_type];
Vicent Marti committed
147
	return git_cache__enabled && object_size < max_size;
148
}
Vicent Marti committed
149

150 151 152 153
static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
{
	khiter_t pos;
	git_cached_obj *entry = NULL;
Vicent Marti committed
154

Vicent Marti committed
155
	if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0)
156 157
		return NULL;

158 159 160
	pos = kh_get(oid, cache->map, oid);
	if (pos != kh_end(cache->map)) {
		entry = kh_val(cache->map, pos);
161

162 163 164 165
		if (flags && entry->flags != flags) {
			entry = NULL;
		} else {
			git_cached_obj_incref(entry);
Vicent Marti committed
166 167
		}
	}
168

169
	git_mutex_unlock(&cache->lock);
Vicent Marti committed
170

171
	return entry;
Vicent Marti committed
172 173
}

174
static void *cache_store(git_cache *cache, git_cached_obj *entry)
Vicent Marti committed
175
{
176
	khiter_t pos;
Vicent Marti committed
177

Vicent Marti committed
178 179
	git_cached_obj_incref(entry);

180 181 182 183 184
	if (!git_cache__enabled && cache->used_memory > 0) {
		git_cache_clear(cache);
		return entry;
	}

Vicent Marti committed
185 186 187
	if (!cache_should_store(entry->type, entry->size))
		return entry;

188 189
	if (git_mutex_lock(&cache->lock) < 0)
		return entry;
190

191 192
	/* soften the load on the cache */
	if (git_cache__current_storage.val > git_cache__max_storage)
193 194
		cache_evict_entries(cache);

195
	pos = kh_get(oid, cache->map, &entry->oid);
Justin Spahr-Summers committed
196

197 198 199
	/* not found */
	if (pos == kh_end(cache->map)) {
		int rval;
200

201 202 203 204 205
		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);
Vicent Marti committed
206
			cache->used_memory += entry->size;
207
			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
		}
	}
	/* 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;
225
		} else {
226
			/* NO OP */
227
		}
Vicent Marti committed
228 229
	}

230
	git_mutex_unlock(&cache->lock);
Vicent Marti committed
231 232
	return entry;
}
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280

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;
		}
	}
}