]> git.proxmox.com Git - libgit2.git/blob - src/cache.c
32ba993b081054de984e1f6990033075f59bfd8e
[libgit2.git] / src / cache.c
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
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
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
65 int git_cache_init(git_cache *cache)
66 {
67 memset(cache, 0, sizeof(*cache));
68
69 if ((git_oidmap_new(&cache->map)) < 0)
70 return -1;
71
72 if (git_rwlock_init(&cache->lock)) {
73 git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
74 return -1;
75 }
76
77 return 0;
78 }
79
80 /* called with lock */
81 static void clear_cache(git_cache *cache)
82 {
83 git_cached_obj *evict = NULL;
84
85 if (git_cache_size(cache) == 0)
86 return;
87
88 git_oidmap_foreach_value(cache->map, evict, {
89 git_cached_obj_decref(evict);
90 });
91
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 void git_cache_clear(git_cache *cache)
98 {
99 if (git_rwlock_wrlock(&cache->lock) < 0)
100 return;
101
102 clear_cache(cache);
103
104 git_rwlock_wrunlock(&cache->lock);
105 }
106
107 void git_cache_dispose(git_cache *cache)
108 {
109 git_cache_clear(cache);
110 git_oidmap_free(cache->map);
111 git_rwlock_free(&cache->lock);
112 git__memzero(cache, sizeof(*cache));
113 }
114
115 /* Called with lock */
116 static void cache_evict_entries(git_cache *cache)
117 {
118 size_t evict_count = git_cache_size(cache) / 2048, i;
119 ssize_t evicted_memory = 0;
120
121 if (evict_count < 8)
122 evict_count = 8;
123
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);
127 return;
128 }
129
130 i = 0;
131 while (evict_count > 0) {
132 git_cached_obj *evict;
133 const git_oid *key;
134
135 if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER)
136 break;
137
138 evict_count--;
139 evicted_memory += evict->size;
140 git_oidmap_delete(cache->map, key);
141 git_cached_obj_decref(evict);
142 }
143
144 cache->used_memory -= evicted_memory;
145 git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
146 }
147
148 static bool cache_should_store(git_object_t object_type, size_t object_size)
149 {
150 size_t max_size = git_cache__max_object_size[object_type];
151 return git_cache__enabled && object_size < max_size;
152 }
153
154 static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
155 {
156 git_cached_obj *entry;
157
158 if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
159 return NULL;
160
161 if ((entry = git_oidmap_get(cache->map, oid)) != NULL) {
162 if (flags && entry->flags != flags) {
163 entry = NULL;
164 } else {
165 git_cached_obj_incref(entry);
166 }
167 }
168
169 git_rwlock_rdunlock(&cache->lock);
170
171 return entry;
172 }
173
174 static void *cache_store(git_cache *cache, git_cached_obj *entry)
175 {
176 git_cached_obj *stored_entry;
177
178 git_cached_obj_incref(entry);
179
180 if (!git_cache__enabled && cache->used_memory > 0) {
181 git_cache_clear(cache);
182 return entry;
183 }
184
185 if (!cache_should_store(entry->type, entry->size))
186 return entry;
187
188 if (git_rwlock_wrlock(&cache->lock) < 0)
189 return entry;
190
191 /* soften the load on the cache */
192 if (git_cache__current_storage.val > git_cache__max_storage)
193 cache_evict_entries(cache);
194
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);
199 cache->used_memory += entry->size;
200 git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
201 }
202 }
203 /* found */
204 else {
205 if (stored_entry->flags == entry->flags) {
206 git_cached_obj_decref(entry);
207 git_cached_obj_incref(stored_entry);
208 entry = stored_entry;
209 } else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
210 entry->flags == GIT_CACHE_STORE_PARSED) {
211 git_cached_obj_decref(stored_entry);
212 git_cached_obj_incref(entry);
213
214 git_oidmap_set(cache->map, &entry->oid, entry);
215 } else {
216 /* NO OP */
217 }
218 }
219
220 git_rwlock_wrunlock(&cache->lock);
221 return entry;
222 }
223
224 void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
225 {
226 entry->cached.flags = GIT_CACHE_STORE_RAW;
227 return cache_store(cache, (git_cached_obj *)entry);
228 }
229
230 void *git_cache_store_parsed(git_cache *cache, git_object *entry)
231 {
232 entry->cached.flags = GIT_CACHE_STORE_PARSED;
233 return cache_store(cache, (git_cached_obj *)entry);
234 }
235
236 git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
237 {
238 return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
239 }
240
241 git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
242 {
243 return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
244 }
245
246 void *git_cache_get_any(git_cache *cache, const git_oid *oid)
247 {
248 return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
249 }
250
251 void git_cached_obj_decref(void *_obj)
252 {
253 git_cached_obj *obj = _obj;
254
255 if (git_atomic_dec(&obj->refcount) == 0) {
256 switch (obj->flags) {
257 case GIT_CACHE_STORE_RAW:
258 git_odb_object__free(_obj);
259 break;
260
261 case GIT_CACHE_STORE_PARSED:
262 git_object__free(_obj);
263 break;
264
265 default:
266 git__free(_obj);
267 break;
268 }
269 }
270 }