cache.c 6.34 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
#include "cache.h"

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

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

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

33 34 35 36 37 38 39 40 41 42 43
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
44 45 46 47
void git_cache_dump_stats(git_cache *cache)
{
	git_cached_obj *object;

48
	if (git_cache_size(cache) == 0)
Vicent Marti committed
49 50
		return;

51 52
	printf("Cache %p: %"PRIuZ" items cached, %"PRIdZ" bytes\n",
		cache, git_cache_size(cache), cache->used_memory);
Vicent Marti committed
53

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

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

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

82
	if (git_cache_size(cache) == 0)
83 84
		return;

85
	git_oidmap_foreach_value(cache->map, evict, {
86 87 88
		git_cached_obj_decref(evict);
	});

89
	git_oidmap_clear(cache->map);
90
	git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
91
	cache->used_memory = 0;
92 93 94 95
}

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

	clear_cache(cache);
100

101
	git_rwlock_wrunlock(&cache->lock);
102 103
}

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

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

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

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

128
		if (git_oidmap_has_data(cache->map, pos)) {
129
			git_cached_obj *evict = git_oidmap_value_at(cache->map, pos);
Vicent Marti committed
130 131

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

135
			git_oidmap_delete_at(cache->map, pos);
Vicent Marti committed
136 137
		}
	}
138 139

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

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

149 150 151 152
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
153

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

157
	pos = git_oidmap_lookup_index(cache->map, oid);
158
	if (git_oidmap_valid_index(cache->map, pos)) {
159
		entry = git_oidmap_value_at(cache->map, pos);
160

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

168
	git_rwlock_rdunlock(&cache->lock);
Vicent Marti committed
169

170
	return entry;
Vicent Marti committed
171 172
}

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

Vicent Marti committed
177 178
	git_cached_obj_incref(entry);

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

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

187
	if (git_rwlock_wrlock(&cache->lock) < 0)
188
		return entry;
189

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

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

196
	/* not found */
197
	if (!git_oidmap_valid_index(cache->map, pos)) {
198
		int rval;
199

200
		git_oidmap_insert(cache->map, &entry->oid, entry, &rval);
201 202
		if (rval >= 0) {
			git_cached_obj_incref(entry);
Vicent Marti committed
203
			cache->used_memory += entry->size;
204
			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
205 206 207 208
		}
	}
	/* found */
	else {
209
		git_cached_obj *stored_entry = git_oidmap_value_at(cache->map, pos);
210 211 212 213 214 215 216 217 218 219

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

220 221
			git_oidmap_set_key_at(cache->map, pos, &entry->oid);
			git_oidmap_set_value_at(cache->map, pos, entry);
222
		} else {
223
			/* NO OP */
224
		}
Vicent Marti committed
225 226
	}

227
	git_rwlock_wrunlock(&cache->lock);
Vicent Marti committed
228 229
	return entry;
}
230 231 232 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

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