cache.c 5.74 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;
}

44 45
int git_cache_init(git_cache *cache)
{
46
	memset(cache, 0, sizeof(*cache));
47 48 49 50

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

51
	if (git_rwlock_init(&cache->lock)) {
52
		git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
Russell Belfer committed
53 54
		return -1;
	}
55

56
	return 0;
Vicent Marti committed
57 58
}

59 60
/* called with lock */
static void clear_cache(git_cache *cache)
61 62 63
{
	git_cached_obj *evict = NULL;

64
	if (git_cache_size(cache) == 0)
65 66
		return;

67
	git_oidmap_foreach_value(cache->map, evict, {
68 69 70
		git_cached_obj_decref(evict);
	});

71
	git_oidmap_clear(cache->map);
72
	git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
73
	cache->used_memory = 0;
74 75 76 77
}

void git_cache_clear(git_cache *cache)
{
78
	if (git_rwlock_wrlock(&cache->lock) < 0)
79 80 81
		return;

	clear_cache(cache);
82

83
	git_rwlock_wrunlock(&cache->lock);
84 85
}

86
void git_cache_dispose(git_cache *cache)
87 88 89
{
	git_cache_clear(cache);
	git_oidmap_free(cache->map);
90
	git_rwlock_free(&cache->lock);
91
	git__memzero(cache, sizeof(*cache));
92 93
}

94 95
/* Called with lock */
static void cache_evict_entries(git_cache *cache)
Vicent Marti committed
96
{
97
	size_t evict_count = git_cache_size(cache) / 2048, i;
98
	ssize_t evicted_memory = 0;
Vicent Marti committed
99

100 101 102
	if (evict_count < 8)
		evict_count = 8;

Vicent Marti committed
103
	/* do not infinite loop if there's not enough entries to evict  */
104
	if (evict_count > git_cache_size(cache)) {
105
		clear_cache(cache);
Vicent Marti committed
106
		return;
107
	}
Vicent Marti committed
108

109
	i = 0;
Vicent Marti committed
110
	while (evict_count > 0) {
111 112
		git_cached_obj *evict;
		const git_oid *key;
Vicent Marti committed
113

114 115
		if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER)
			break;
Vicent Marti committed
116

117 118 119
		evict_count--;
		evicted_memory += evict->size;
		git_oidmap_delete(cache->map, key);
120
		git_cached_obj_decref(evict);
Vicent Marti committed
121
	}
122 123

	cache->used_memory -= evicted_memory;
124
	git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
Vicent Marti committed
125 126
}

127
static bool cache_should_store(git_object_t object_type, size_t object_size)
Vicent Marti committed
128
{
Vicent Marti committed
129
	size_t max_size = git_cache__max_object_size[object_type];
Vicent Marti committed
130
	return git_cache__enabled && object_size < max_size;
131
}
Vicent Marti committed
132

133 134
static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
{
135
	git_cached_obj *entry;
Vicent Marti committed
136

137
	if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
138 139
		return NULL;

140
	if ((entry = git_oidmap_get(cache->map, oid)) != NULL) {
141 142 143 144
		if (flags && entry->flags != flags) {
			entry = NULL;
		} else {
			git_cached_obj_incref(entry);
Vicent Marti committed
145 146
		}
	}
147

148
	git_rwlock_rdunlock(&cache->lock);
Vicent Marti committed
149

150
	return entry;
Vicent Marti committed
151 152
}

153
static void *cache_store(git_cache *cache, git_cached_obj *entry)
Vicent Marti committed
154
{
155
	git_cached_obj *stored_entry;
Vicent Marti committed
156

Vicent Marti committed
157 158
	git_cached_obj_incref(entry);

159 160 161 162 163
	if (!git_cache__enabled && cache->used_memory > 0) {
		git_cache_clear(cache);
		return entry;
	}

Vicent Marti committed
164 165 166
	if (!cache_should_store(entry->type, entry->size))
		return entry;

167
	if (git_rwlock_wrlock(&cache->lock) < 0)
168
		return entry;
169

170 171
	/* soften the load on the cache */
	if (git_cache__current_storage.val > git_cache__max_storage)
172 173
		cache_evict_entries(cache);

174
	/* not found */
175
	if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) {
176
		if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
177
			git_cached_obj_incref(entry);
Vicent Marti committed
178
			cache->used_memory += entry->size;
179
			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
180 181 182 183 184 185 186 187 188
		}
	}
	/* 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 &&
189
			   entry->flags == GIT_CACHE_STORE_PARSED) {
190 191 192 193 194 195 196 197
			if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
				git_cached_obj_decref(stored_entry);
				git_cached_obj_incref(entry);
			} else {
				git_cached_obj_decref(entry);
				git_cached_obj_incref(stored_entry);
				entry = stored_entry;
			}
198
		} else {
199
			/* NO OP */
200
		}
Vicent Marti committed
201 202
	}

203
	git_rwlock_wrunlock(&cache->lock);
Vicent Marti committed
204 205
	return entry;
}
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 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

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