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