cache.c 6.11 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
static size_t git_cache__max_object_size[8] = {
23 24 25 26 27 28 29 30
	0,     /* GIT_OBJECT__EXT1 */
	4096,  /* GIT_OBJECT_COMMIT */
	4096,  /* GIT_OBJECT_TREE */
	0,     /* GIT_OBJECT_BLOB */
	4096,  /* GIT_OBJECT_TAG */
	0,     /* GIT_OBJECT__EXT2 */
	0,     /* GIT_OBJECT_OFS_DELTA */
	0      /* GIT_OBJECT_REF_DELTA */
Vicent Marti committed
31 32
};

33
int git_cache_set_max_object_size(git_object_t type, size_t size)
34 35
{
	if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
36
		git_error_set(GIT_ERROR_INVALID, "type out of range");
37 38 39 40 41 42 43
		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 69 70 71

	if ((git_oidmap_new(&cache->map)) < 0)
		return -1;

72
	if (git_rwlock_init(&cache->lock)) {
73
		git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
Russell Belfer committed
74 75
		return -1;
	}
76

77
	return 0;
Vicent Marti committed
78 79
}

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

85
	if (git_cache_size(cache) == 0)
86 87
		return;

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

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

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

	clear_cache(cache);
103

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

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

115 116
/* Called with lock */
static void cache_evict_entries(git_cache *cache)
Vicent Marti committed
117
{
118
	size_t evict_count = git_cache_size(cache) / 2048, i;
119
	ssize_t evicted_memory = 0;
Vicent Marti committed
120

121 122 123
	if (evict_count < 8)
		evict_count = 8;

Vicent Marti committed
124
	/* do not infinite loop if there's not enough entries to evict  */
125
	if (evict_count > git_cache_size(cache)) {
126
		clear_cache(cache);
Vicent Marti committed
127
		return;
128
	}
Vicent Marti committed
129

130
	i = 0;
Vicent Marti committed
131
	while (evict_count > 0) {
132 133
		git_cached_obj *evict;
		const git_oid *key;
Vicent Marti committed
134

135 136
		if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER)
			break;
Vicent Marti committed
137

138 139 140
		evict_count--;
		evicted_memory += evict->size;
		git_oidmap_delete(cache->map, key);
141
		git_cached_obj_decref(evict);
Vicent Marti committed
142
	}
143 144

	cache->used_memory -= evicted_memory;
145
	git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
Vicent Marti committed
146 147
}

148
static bool cache_should_store(git_object_t object_type, size_t object_size)
Vicent Marti committed
149
{
Vicent Marti committed
150
	size_t max_size = git_cache__max_object_size[object_type];
Vicent Marti committed
151
	return git_cache__enabled && object_size < max_size;
152
}
Vicent Marti committed
153

154 155
static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
{
156
	git_cached_obj *entry;
Vicent Marti committed
157

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

161
	if ((entry = git_oidmap_get(cache->map, oid)) != NULL) {
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_rwlock_rdunlock(&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
	git_cached_obj *stored_entry;
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
	if (git_rwlock_wrlock(&cache->lock) < 0)
189
		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
	/* not found */
196
	if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) {
197
		if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
198
			git_cached_obj_incref(entry);
Vicent Marti committed
199
			cache->used_memory += entry->size;
200
			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
201 202 203 204 205 206 207 208 209
		}
	}
	/* found */
	else {
		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 &&
210
			   entry->flags == GIT_CACHE_STORE_PARSED) {
211 212 213
			git_cached_obj_decref(stored_entry);
			git_cached_obj_incref(entry);

214
			git_oidmap_set(cache->map, &entry->oid, entry);
215
		} else {
216
			/* NO OP */
217
		}
Vicent Marti committed
218 219
	}

220
	git_rwlock_wrunlock(&cache->lock);
Vicent Marti committed
221 222
	return entry;
}
223 224 225 226 227 228 229 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

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