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.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_init(git_cache * cache)44 int git_cache_init(git_cache *cache)
45 {
46 	memset(cache, 0, sizeof(*cache));
47 
48 	if ((git_oidmap_new(&cache->map)) < 0)
49 		return -1;
50 
51 	if (git_rwlock_init(&cache->lock)) {
52 		git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
53 		return -1;
54 	}
55 
56 	return 0;
57 }
58 
59 /* called with lock */
clear_cache(git_cache * cache)60 static void clear_cache(git_cache *cache)
61 {
62 	git_cached_obj *evict = NULL;
63 
64 	if (git_cache_size(cache) == 0)
65 		return;
66 
67 	git_oidmap_foreach_value(cache->map, evict, {
68 		git_cached_obj_decref(evict);
69 	});
70 
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 
git_cache_clear(git_cache * cache)76 void git_cache_clear(git_cache *cache)
77 {
78 	if (git_rwlock_wrlock(&cache->lock) < 0)
79 		return;
80 
81 	clear_cache(cache);
82 
83 	git_rwlock_wrunlock(&cache->lock);
84 }
85 
git_cache_dispose(git_cache * cache)86 void git_cache_dispose(git_cache *cache)
87 {
88 	git_cache_clear(cache);
89 	git_oidmap_free(cache->map);
90 	git_rwlock_free(&cache->lock);
91 	git__memzero(cache, sizeof(*cache));
92 }
93 
94 /* Called with lock */
cache_evict_entries(git_cache * cache)95 static void cache_evict_entries(git_cache *cache)
96 {
97 	size_t evict_count = git_cache_size(cache) / 2048, i;
98 	ssize_t evicted_memory = 0;
99 
100 	if (evict_count < 8)
101 		evict_count = 8;
102 
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);
106 		return;
107 	}
108 
109 	i = 0;
110 	while (evict_count > 0) {
111 		git_cached_obj *evict;
112 		const git_oid *key;
113 
114 		if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER)
115 			break;
116 
117 		evict_count--;
118 		evicted_memory += evict->size;
119 		git_oidmap_delete(cache->map, key);
120 		git_cached_obj_decref(evict);
121 	}
122 
123 	cache->used_memory -= evicted_memory;
124 	git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
125 }
126 
cache_should_store(git_object_t object_type,size_t object_size)127 static bool cache_should_store(git_object_t object_type, size_t object_size)
128 {
129 	size_t max_size = git_cache__max_object_size[object_type];
130 	return git_cache__enabled && object_size < max_size;
131 }
132 
cache_get(git_cache * cache,const git_oid * oid,unsigned int flags)133 static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
134 {
135 	git_cached_obj *entry;
136 
137 	if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
138 		return NULL;
139 
140 	if ((entry = git_oidmap_get(cache->map, oid)) != NULL) {
141 		if (flags && entry->flags != flags) {
142 			entry = NULL;
143 		} else {
144 			git_cached_obj_incref(entry);
145 		}
146 	}
147 
148 	git_rwlock_rdunlock(&cache->lock);
149 
150 	return entry;
151 }
152 
cache_store(git_cache * cache,git_cached_obj * entry)153 static void *cache_store(git_cache *cache, git_cached_obj *entry)
154 {
155 	git_cached_obj *stored_entry;
156 
157 	git_cached_obj_incref(entry);
158 
159 	if (!git_cache__enabled && cache->used_memory > 0) {
160 		git_cache_clear(cache);
161 		return entry;
162 	}
163 
164 	if (!cache_should_store(entry->type, entry->size))
165 		return entry;
166 
167 	if (git_rwlock_wrlock(&cache->lock) < 0)
168 		return entry;
169 
170 	/* soften the load on the cache */
171 	if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage)
172 		cache_evict_entries(cache);
173 
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);
178 			cache->used_memory += entry->size;
179 			git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
180 		}
181 	}
182 	/* found */
183 	else {
184 		if (stored_entry->flags == entry->flags) {
185 			git_cached_obj_decref(entry);
186 			git_cached_obj_incref(stored_entry);
187 			entry = stored_entry;
188 		} else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
189 			   entry->flags == GIT_CACHE_STORE_PARSED) {
190 			if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
191 				git_cached_obj_decref(stored_entry);
192 				git_cached_obj_incref(entry);
193 			} else {
194 				git_cached_obj_decref(entry);
195 				git_cached_obj_incref(stored_entry);
196 				entry = stored_entry;
197 			}
198 		} else {
199 			/* NO OP */
200 		}
201 	}
202 
203 	git_rwlock_wrunlock(&cache->lock);
204 	return entry;
205 }
206 
git_cache_store_raw(git_cache * cache,git_odb_object * entry)207 void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
208 {
209 	entry->cached.flags = GIT_CACHE_STORE_RAW;
210 	return cache_store(cache, (git_cached_obj *)entry);
211 }
212 
git_cache_store_parsed(git_cache * cache,git_object * entry)213 void *git_cache_store_parsed(git_cache *cache, git_object *entry)
214 {
215 	entry->cached.flags = GIT_CACHE_STORE_PARSED;
216 	return cache_store(cache, (git_cached_obj *)entry);
217 }
218 
git_cache_get_raw(git_cache * cache,const git_oid * oid)219 git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
220 {
221 	return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
222 }
223 
git_cache_get_parsed(git_cache * cache,const git_oid * oid)224 git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
225 {
226 	return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
227 }
228 
git_cache_get_any(git_cache * cache,const git_oid * oid)229 void *git_cache_get_any(git_cache *cache, const git_oid *oid)
230 {
231 	return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
232 }
233 
git_cached_obj_decref(void * _obj)234 void git_cached_obj_decref(void *_obj)
235 {
236 	git_cached_obj *obj = _obj;
237 
238 	if (git_atomic32_dec(&obj->refcount) == 0) {
239 		switch (obj->flags) {
240 		case GIT_CACHE_STORE_RAW:
241 			git_odb_object__free(_obj);
242 			break;
243 
244 		case GIT_CACHE_STORE_PARSED:
245 			git_object__free(_obj);
246 			break;
247 
248 		default:
249 			git__free(_obj);
250 			break;
251 		}
252 	}
253 }
254