cache.c 6.27 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
void git_cache_dump_stats(git_cache *cache)
{
	git_cached_obj *object;

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

53 54
	printf("Cache %p: %d items cached, %"PRIdZ" bytes\n",
		cache, kh_size(cache->map), cache->used_memory);
Vicent Marti committed
55 56 57

	kh_foreach_value(cache->map, object, {
		char oid_str[9];
58
		printf(" %s%c %s (%"PRIuZ")\n",
Vicent Marti committed
59 60 61
			git_object_type2string(object->type),
			object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
			git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
62
			object->size
Vicent Marti committed
63 64 65 66
		);
	});
}

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

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

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

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

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

void git_cache_clear(git_cache *cache)
{
98
	if (git_rwlock_wrlock(&cache->lock) < 0)
99 100 101
		return;

	clear_cache(cache);
102

103
	git_rwlock_wrunlock(&cache->lock);
104 105
}

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

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

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

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

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

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

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

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

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

151 152 153 154
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
155

156
	if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
157 158
		return NULL;

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

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

170
	git_rwlock_rdunlock(&cache->lock);
Vicent Marti committed
171

172
	return entry;
Vicent Marti committed
173 174
}

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

Vicent Marti committed
179 180
	git_cached_obj_incref(entry);

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

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

189
	if (git_rwlock_wrlock(&cache->lock) < 0)
190
		return entry;
191

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

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

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

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

231
	git_rwlock_wrunlock(&cache->lock);
Vicent Marti committed
232 233
	return entry;
}
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 281

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