]> git.proxmox.com Git - libgit2.git/blob - src/cache.c
Merge pull request #4130 from libgit2/ethomson/clar_messages
[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 "common.h"
9 #include "repository.h"
10 #include "commit.h"
11 #include "thread-utils.h"
12 #include "util.h"
13 #include "cache.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_OBJ__EXT1 */
24 4096, /* GIT_OBJ_COMMIT */
25 4096, /* GIT_OBJ_TREE */
26 0, /* GIT_OBJ_BLOB */
27 4096, /* GIT_OBJ_TAG */
28 0, /* GIT_OBJ__EXT2 */
29 0, /* GIT_OBJ_OFS_DELTA */
30 0 /* GIT_OBJ_REF_DELTA */
31 };
32
33 int git_cache_set_max_object_size(git_otype type, size_t size)
34 {
35 if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
36 giterr_set(GITERR_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 cache->map = git_oidmap_alloc();
69 GITERR_CHECK_ALLOC(cache->map);
70 if (git_rwlock_init(&cache->lock)) {
71 giterr_set(GITERR_OS, "failed to initialize cache rwlock");
72 return -1;
73 }
74 return 0;
75 }
76
77 /* called with lock */
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
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
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 */
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 khiter_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
143 static bool cache_should_store(git_otype 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
149 static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
150 {
151 khiter_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
173 static void *cache_store(git_cache *cache, git_cached_obj *entry)
174 {
175 khiter_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
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
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
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
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
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
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 }