1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "cache.h"
9 
10 #include "repository.h"
11 #include "commit.h"
12 #include "thread-utils.h"
13 #include "util.h"
14 #include "odb.h"
15 #include "object.h"
16 #include "git2/oid.h"
17 
18 bool git_cache__enabled = true;
19 ssize_t git_cache__max_storage = (256 * 1024 * 1024);
20 git_atomic_ssize git_cache__current_storage = {0};
21 
22 static size_t git_cache__max_object_size[8] = {
23 	0,     /* GIT_OBJECT__EXT1 */
24 	4096,  /* GIT_OBJECT_COMMIT */
25 	4096,  /* GIT_OBJECT_TREE */
26 	0,     /* GIT_OBJECT_BLOB */
27 	4096,  /* GIT_OBJECT_TAG */
28 	0,     /* GIT_OBJECT__EXT2 */
29 	0,     /* GIT_OBJECT_OFS_DELTA */
30 	0      /* GIT_OBJECT_REF_DELTA */
31 };
32 
git_cache_set_max_object_size(git_object_t type,size_t size)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 		return -1;
38 	}
39 
40 	git_cache__max_object_size[type] = size;
41 	return 0;
42 }
43 
git_cache_dump_stats(git_cache * cache)44 void git_cache_dump_stats(git_cache *cache)
45 {
46 	git_cached_obj *object;
47 
48 	if (git_cache_size(cache) == 0)
49 		return;
50 
51 	printf("Cache %p: %"PRIuZ" items cached, %"PRIdZ" bytes\n",
52 		cache, git_cache_size(cache), cache->used_memory);
53 
54 	git_oidmap_foreach_value(cache->map, object, {
55 		char oid_str[9];
56 		printf(" %s%c %s (%"PRIuZ")\n",
57 			git_object_type2string(object->type),
58 			object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
59 			git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
60 			object->size
61 		);
62 	});
63 }
64 
git_cache_init(git_cache * cache)65 int git_cache_init(git_cache *cache)
66 {
67 	memset(cache, 0, sizeof(*cache));
68 	cache->map = git_oidmap_alloc();
69 	GIT_ERROR_CHECK_ALLOC(cache->map);
70 	if (git_rwlock_init(&cache->lock)) {
71 		git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
72 		return -1;
73 	}
74 	return 0;
75 }
76 
77 /* called with lock */
clear_cache(git_cache * cache)78 static void clear_cache(git_cache *cache)
79 {
80 	git_cached_obj *evict = NULL;
81 
82 	if (git_cache_size(cache) == 0)
83 		return;
84 
85 	git_oidmap_foreach_value(cache->map, evict, {
86 		git_cached_obj_decref(evict);
87 	});
88 
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 
git_cache_clear(git_cache * cache)94 void git_cache_clear(git_cache *cache)
95 {
96 	if (git_rwlock_wrlock(&cache->lock) < 0)
97 		return;
98 
99 	clear_cache(cache);
100 
101 	git_rwlock_wrunlock(&cache->lock);
102 }
103 
git_cache_free(git_cache * cache)104 void git_cache_free(git_cache *cache)
105 {
106 	git_cache_clear(cache);
107 	git_oidmap_free(cache->map);
108 	git_rwlock_free(&cache->lock);
109 	git__memzero(cache, sizeof(*cache));
110 }
111 
112 /* Called with lock */
cache_evict_entries(git_cache * cache)113 static void cache_evict_entries(git_cache *cache)
114 {
115 	uint32_t seed = rand();
116 	size_t evict_count = 8;
117 	ssize_t evicted_memory = 0;
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);
122 		return;
123 	}
124 
125 	while (evict_count > 0) {
126 		size_t pos = seed++ % git_oidmap_end(cache->map);
127 
128 		if (git_oidmap_has_data(cache->map, pos)) {
129 			git_cached_obj *evict = git_oidmap_value_at(cache->map, pos);
130 
131 			evict_count--;
132 			evicted_memory += evict->size;
133 			git_cached_obj_decref(evict);
134 
135 			git_oidmap_delete_at(cache->map, pos);
136 		}
137 	}
138 
139 	cache->used_memory -= evicted_memory;
140 	git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
141 }
142 
cache_should_store(git_object_t object_type,size_t object_size)143 static bool cache_should_store(git_object_t object_type, size_t object_size)
144 {
145 	size_t max_size = git_cache__max_object_size[object_type];
146 	return git_cache__enabled && object_size < max_size;
147 }
148 
cache_get(git_cache * cache,const git_oid * oid,unsigned int flags)149 static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
150 {
151 	size_t pos;
152 	git_cached_obj *entry = NULL;
153 
154 	if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
155 		return NULL;
156 
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 		if (flags && entry->flags != flags) {
162 			entry = NULL;
163 		} else {
164 			git_cached_obj_incref(entry);
165 		}
166 	}
167 
168 	git_rwlock_rdunlock(&cache->lock);
169 
170 	return entry;
171 }
172 
cache_store(git_cache * cache,git_cached_obj * entry)173 static void *cache_store(git_cache *cache, git_cached_obj *entry)
174 {
175 	size_t pos;
176 
177 	git_cached_obj_incref(entry);
178 
179 	if (!git_cache__enabled && cache->used_memory > 0) {
180 		git_cache_clear(cache);
181 		return entry;
182 	}
183 
184 	if (!cache_should_store(entry->type, entry->size))
185 		return entry;
186 
187 	if (git_rwlock_wrlock(&cache->lock) < 0)
188 		return entry;
189 
190 	/* soften the load on the cache */
191 	if (git_cache__current_storage.val > git_cache__max_storage)
192 		cache_evict_entries(cache);
193 
194 	pos = git_oidmap_lookup_index(cache->map, &entry->oid);
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 		if (rval >= 0) {
202 			git_cached_obj_incref(entry);
203 			cache->used_memory += entry->size;
204 			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
205 		}
206 	}
207 	/* found */
208 	else {
209 		git_cached_obj *stored_entry = git_oidmap_value_at(cache->map, pos);
210 
211 		if (stored_entry->flags == entry->flags) {
212 			git_cached_obj_decref(entry);
213 			git_cached_obj_incref(stored_entry);
214 			entry = stored_entry;
215 		} else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
216 			entry->flags == GIT_CACHE_STORE_PARSED) {
217 			git_cached_obj_decref(stored_entry);
218 			git_cached_obj_incref(entry);
219 
220 			git_oidmap_set_key_at(cache->map, pos, &entry->oid);
221 			git_oidmap_set_value_at(cache->map, pos, entry);
222 		} else {
223 			/* NO OP */
224 		}
225 	}
226 
227 	git_rwlock_wrunlock(&cache->lock);
228 	return entry;
229 }
230 
git_cache_store_raw(git_cache * cache,git_odb_object * entry)231 void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
232 {
233 	entry->cached.flags = GIT_CACHE_STORE_RAW;
234 	return cache_store(cache, (git_cached_obj *)entry);
235 }
236 
git_cache_store_parsed(git_cache * cache,git_object * entry)237 void *git_cache_store_parsed(git_cache *cache, git_object *entry)
238 {
239 	entry->cached.flags = GIT_CACHE_STORE_PARSED;
240 	return cache_store(cache, (git_cached_obj *)entry);
241 }
242 
git_cache_get_raw(git_cache * cache,const git_oid * oid)243 git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
244 {
245 	return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
246 }
247 
git_cache_get_parsed(git_cache * cache,const git_oid * oid)248 git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
249 {
250 	return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
251 }
252 
git_cache_get_any(git_cache * cache,const git_oid * oid)253 void *git_cache_get_any(git_cache *cache, const git_oid *oid)
254 {
255 	return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
256 }
257 
git_cached_obj_decref(void * _obj)258 void git_cached_obj_decref(void *_obj)
259 {
260 	git_cached_obj *obj = _obj;
261 
262 	if (git_atomic_dec(&obj->refcount) == 0) {
263 		switch (obj->flags) {
264 		case GIT_CACHE_STORE_RAW:
265 			git_odb_object__free(_obj);
266 			break;
267 
268 		case GIT_CACHE_STORE_PARSED:
269 			git_object__free(_obj);
270 			break;
271 
272 		default:
273 			git__free(_obj);
274 			break;
275 		}
276 	}
277 }
278